From 5ef82651852f7d0a1ded5b8c124e080e409b1696 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 12:01:00 +0100 Subject: [PATCH 01/47] feat: split test runner into a separate package --- packages/runner/README.md | 1 + packages/runner/package.json | 41 +++ packages/runner/rollup.config.js | 56 ++++ packages/runner/src/chain.ts | 32 +++ packages/runner/src/collect.ts | 91 ++++++ packages/runner/src/context.ts | 73 +++++ packages/runner/src/error.ts | 196 +++++++++++++ packages/runner/src/index.ts | 3 + packages/runner/src/map.ts | 22 ++ packages/runner/src/run.ts | 405 +++++++++++++++++++++++++++ packages/runner/src/setup.ts | 13 + packages/runner/src/suite.ts | 251 +++++++++++++++++ packages/runner/src/test-state.ts | 11 + packages/runner/src/types/index.ts | 2 + packages/runner/src/types/runner.ts | 36 +++ packages/runner/src/types/tasks.ts | 235 ++++++++++++++++ packages/runner/src/utils/collect.ts | 94 +++++++ packages/runner/src/utils/index.ts | 3 + packages/runner/src/utils/suite.ts | 22 ++ packages/runner/src/utils/tasks.ts | 45 +++ packages/runner/utils.d.ts | 1 + 21 files changed, 1633 insertions(+) create mode 100644 packages/runner/README.md create mode 100644 packages/runner/package.json create mode 100644 packages/runner/rollup.config.js create mode 100644 packages/runner/src/chain.ts create mode 100644 packages/runner/src/collect.ts create mode 100644 packages/runner/src/context.ts create mode 100644 packages/runner/src/error.ts create mode 100644 packages/runner/src/index.ts create mode 100644 packages/runner/src/map.ts create mode 100644 packages/runner/src/run.ts create mode 100644 packages/runner/src/setup.ts create mode 100644 packages/runner/src/suite.ts create mode 100644 packages/runner/src/test-state.ts create mode 100644 packages/runner/src/types/index.ts create mode 100644 packages/runner/src/types/runner.ts create mode 100644 packages/runner/src/types/tasks.ts create mode 100644 packages/runner/src/utils/collect.ts create mode 100644 packages/runner/src/utils/index.ts create mode 100644 packages/runner/src/utils/suite.ts create mode 100644 packages/runner/src/utils/tasks.ts create mode 100644 packages/runner/utils.d.ts diff --git a/packages/runner/README.md b/packages/runner/README.md new file mode 100644 index 000000000000..9b17bf5a4d1d --- /dev/null +++ b/packages/runner/README.md @@ -0,0 +1 @@ +# @vitest/runner diff --git a/packages/runner/package.json b/packages/runner/package.json new file mode 100644 index 000000000000..fee8a8ee37bf --- /dev/null +++ b/packages/runner/package.json @@ -0,0 +1,41 @@ +{ + "name": "@vitest/runner", + "type": "module", + "version": "0.27.2", + "description": "Vitest test runner", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/vitest-dev/vitest.git", + "directory": "packages/runner" + }, + "sideEffects": true, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./utils": { + "types": "./dist/utils.d.ts", + "import": "./dist/utils.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": { + "@vitest/utils": "workspace:*", + "chai": "^4.3.7", + "p-limit": "^4.0.0" + } +} diff --git a/packages/runner/rollup.config.js b/packages/runner/rollup.config.js new file mode 100644 index 000000000000..c0bd0c6dcbcb --- /dev/null +++ b/packages/runner/rollup.config.js @@ -0,0 +1,56 @@ +import { builtinModules } from 'module' +import esbuild from 'rollup-plugin-esbuild' +import dts from 'rollup-plugin-dts' +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', + utils: 'src/utils/index.ts', +} + +const plugins = [ + 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) +} diff --git a/packages/runner/src/chain.ts b/packages/runner/src/chain.ts new file mode 100644 index 000000000000..12db6fbb125b --- /dev/null +++ b/packages/runner/src/chain.ts @@ -0,0 +1,32 @@ +export type ChainableFunction = { + (...args: Args): R +} & { + [x in T]: ChainableFunction +} & { + fn: (this: Record, ...args: Args) => R +} & E + +export function createChainable( + keys: T[], + fn: (this: Record, ...args: Args) => R, +): ChainableFunction { + function create(context: Record) { + const chain = function (this: any, ...args: Args) { + return fn.apply(context, args) + } + Object.assign(chain, fn) + chain.withContext = () => chain.bind(context) + for (const key of keys) { + Object.defineProperty(chain, key, { + get() { + return create({ ...context, [key]: true }) + }, + }) + } + return chain + } + + const chain = create({} as any) as any + chain.fn = fn + return chain +} diff --git a/packages/runner/src/collect.ts b/packages/runner/src/collect.ts new file mode 100644 index 000000000000..6b0af59f30ca --- /dev/null +++ b/packages/runner/src/collect.ts @@ -0,0 +1,91 @@ +import { slash } from '@vitest/utils' +import type { File } from './types' +import type { VitestRunner } from './types/runner' +import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from './utils/collect' +import { clearCollectorContext, getDefaultSuite } from './suite' +import { getHooks, setHooks } from './map' +import { processError } from './error' +import { collectorContext } from './context' +import { runSetupFiles } from './setup' + +const now = Date.now + +export async function collectTests(paths: string[], runner: VitestRunner): Promise { + const files: File[] = [] + // TODO: move to if(browser) + // const browserHashMap = getWorkerState().browserHashMap! + + // async function importFromBrowser(filepath: string) { + // const match = filepath.match(/^(\w:\/)/) + // const hash = browserHashMap.get(filepath) + // if (match) + // return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) + // else + // return await import(`${filepath}?v=${hash}`) + // } + + const config = runner.config + + for (const filepath of paths) { + const url = new URL(filepath, `file://${config.root}`) + const isWindows = /^\w\:\//.test(config.root) + const path = slash(url.href.slice(isWindows ? 8 : 7)) + const file: File = { + id: generateHash(path), + name: path, + type: 'suite', + mode: 'run', + filepath, + tasks: [], + projectName: config.name, + } + + clearCollectorContext(runner) + + try { + const setupStart = now() + await runSetupFiles(config, runner) + + const collectStart = now() + file.setupDuration = collectStart - setupStart + + await runner.importFile(filepath) + + const defaultTasks = await getDefaultSuite().collect(file) + + setHooks(file, getHooks(defaultTasks)) + + for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) { + if (c.type === 'test') { + file.tasks.push(c) + } + else if (c.type === 'suite') { + file.tasks.push(c) + } + else if (c.type === 'collector') { + const suite = await c.collect(file) + if (suite.name || suite.tasks.length) + file.tasks.push(suite) + } + } + file.collectDuration = now() - collectStart + } + catch (e) { + const error = processError(e) + file.result = { + state: 'fail', + error, + errors: [error], + } + } + + calculateSuiteHash(file) + + const hasOnlyTasks = someTasksAreOnly(file) + interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly) + + files.push(file) + } + + return files +} diff --git a/packages/runner/src/context.ts b/packages/runner/src/context.ts new file mode 100644 index 000000000000..4278735bc656 --- /dev/null +++ b/packages/runner/src/context.ts @@ -0,0 +1,73 @@ +import type { Awaitable } from '@vitest/utils' +import { clearTimeout, setTimeout } from '@vitest/utils' +import type { RuntimeContext, SuiteCollector, Test, TestContext } from './types' +// import { getWorkerState } from '../utils' +import type { VitestRunner } from './types/runner' + +export const collectorContext: RuntimeContext = { + tasks: [], + currentSuite: null, +} + +export function collectTask(task: SuiteCollector) { + collectorContext.currentSuite?.tasks.push(task) +} + +export async function runWithSuite(suite: SuiteCollector, fn: (() => Awaitable)) { + const prev = collectorContext.currentSuite + collectorContext.currentSuite = suite + await fn() + collectorContext.currentSuite = prev +} + +export function withTimeout any)>( + fn: T, + timeout: number, + isHook = false, +): T { + if (timeout <= 0 || timeout === Infinity) + return fn + + return ((...args: (T extends ((...args: infer A) => any) ? A : never)) => { + return Promise.race([fn(...args), new Promise((resolve, reject) => { + const timer = setTimeout(() => { + clearTimeout(timer) + reject(new Error(makeTimeoutMsg(isHook, timeout))) + }, timeout) + // `unref` might not exist in browser + timer.unref?.() + })]) as Awaitable + }) as T +} + +export function createTestContext(test: Test, runner: VitestRunner): TestContext { + const context = function () { + throw new Error('done() callback is deprecated, use promise instead') + } as unknown as TestContext + + context.meta = test + + // let _expect: Vi.ExpectStatic | undefined + // Object.defineProperty(context, 'expect', { + // get() { + // if (!_expect) + // _expect = createExpect(test) + // return _expect + // }, + // }) + // Object.defineProperty(context, '_local', { + // get() { + // return _expect != null + // }, + // }) + context.onTestFailed = (fn) => { + test.onFailed ||= [] + test.onFailed.push(fn) + } + + return runner.augmentTestContext?.(context) || context +} + +function makeTimeoutMsg(isHook: boolean, timeout: number) { + return `${isHook ? 'Hook' : 'Test'} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? 'hook' : 'test'}, pass a timeout value as the last argument or configure it globally with "${isHook ? 'hookTimeout' : 'testTimeout'}".` +} diff --git a/packages/runner/src/error.ts b/packages/runner/src/error.ts new file mode 100644 index 000000000000..34647659cfdc --- /dev/null +++ b/packages/runner/src/error.ts @@ -0,0 +1,196 @@ +import { deepClone, format, getOwnProperties, getType, stringify } from '@vitest/utils' + +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 + actual?: any + expected?: any + operator?: string + type?: string + frame?: string +} + +const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@' +const IS_COLLECTION_SYMBOL = '@@__IMMUTABLE_ITERABLE__@@' + +const isImmutable = (v: any) => v && (v[IS_COLLECTION_SYMBOL] || v[IS_RECORD_SYMBOL]) + +const OBJECT_PROTO = Object.getPrototypeOf({}) + +function getUnserializableMessage(err: unknown) { + if (err instanceof Error) + return `: ${err.message}` + if (typeof err === 'string') + return `: ${err}` + return '' +} + +// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm +export function serializeError(val: any, seen = new WeakMap()): any { + if (!val || typeof val === 'string') + return val + if (typeof val === 'function') + return `Function<${val.name || 'anonymous'}>` + if (typeof val === 'symbol') + return val.toString() + if (typeof val !== 'object') + return val + // cannot serialize immutables as immutables + if (isImmutable(val)) + return serializeError(val.toJSON(), seen) + if (val instanceof Promise || (val.constructor && val.constructor.prototype === 'AsyncFunction')) + return 'Promise' + if (typeof Element !== 'undefined' && val instanceof Element) + return val.tagName + if (typeof val.asymmetricMatch === 'function') + return `${val.toString()} ${format(val.sample)}` + + if (seen.has(val)) + return seen.get(val) + + if (Array.isArray(val)) { + const clone: any[] = new Array(val.length) + seen.set(val, clone) + val.forEach((e, i) => { + try { + clone[i] = serializeError(e, seen) + } + catch (err) { + clone[i] = getUnserializableMessage(err) + } + }) + return clone + } + else { + // Objects with `Error` constructors appear to cause problems during worker communication + // using `MessagePort`, so the serialized error object is being recreated as plain object. + const clone = Object.create(null) + seen.set(val, clone) + + let obj = val + while (obj && obj !== OBJECT_PROTO) { + Object.getOwnPropertyNames(obj).forEach((key) => { + if (key in clone) + return + try { + clone[key] = serializeError(val[key], seen) + } + catch (err) { + // delete in case it has a setter from prototype that might throw + delete clone[key] + clone[key] = getUnserializableMessage(err) + } + }) + obj = Object.getPrototypeOf(obj) + } + return clone + } +} + +function normalizeErrorMessage(message: string) { + return message.replace(/__vite_ssr_import_\d+__\./g, '') +} + +interface ProcessErrorOptions { + outputDiffMaxSize?: number +} + +export function processError(err: any, options: ProcessErrorOptions = {}) { + if (!err || typeof err !== 'object') + return err + // stack is not serialized in worker communication + // we stringify it first + if (err.stack) + err.stackStr = String(err.stack) + if (err.name) + err.nameStr = String(err.name) + + const clonedActual = deepClone(err.actual) + const clonedExpected = deepClone(err.expected) + + const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected) + + err.actual = replacedActual + err.expected = replacedExpected + + const maxDiffSize = options.outputDiffMaxSize ?? 10000 + + if (typeof err.expected !== 'string') + err.expected = stringify(err.expected, 10, { maxLength: maxDiffSize }) + if (typeof err.actual !== 'string') + err.actual = stringify(err.actual, 10, { maxLength: maxDiffSize }) + + // some Error implementations don't allow rewriting message + try { + if (typeof err.message === 'string') + err.message = normalizeErrorMessage(err.message) + + if (typeof err.cause === 'object' && typeof err.cause.message === 'string') + err.cause.message = normalizeErrorMessage(err.cause.message) + } + catch {} + + try { + return serializeError(err) + } + catch (e: any) { + return serializeError(new Error(`Failed to fully serialize error: ${e?.message}\nInner error message: ${err?.message}`)) + } +} + +function isAsymmetricMatcher(data: any) { + const type = getType(data) + return type === 'Object' && typeof data.asymmetricMatch === 'function' +} + +function isReplaceable(obj1: any, obj2: any) { + const obj1Type = getType(obj1) + const obj2Type = getType(obj2) + return obj1Type === obj2Type && obj1Type === 'Object' +} + +export function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced = new WeakSet(), expectedReplaced = new WeakSet()) { + if (!isReplaceable(actual, expected)) + return { replacedActual: actual, replacedExpected: expected } + if (actualReplaced.has(actual) || expectedReplaced.has(expected)) + return { replacedActual: actual, replacedExpected: expected } + actualReplaced.add(actual) + expectedReplaced.add(expected) + getOwnProperties(expected).forEach((key) => { + const expectedValue = expected[key] + const actualValue = actual[key] + if (isAsymmetricMatcher(expectedValue)) { + if (expectedValue.asymmetricMatch(actualValue)) + actual[key] = expectedValue + } + else if (isAsymmetricMatcher(actualValue)) { + if (actualValue.asymmetricMatch(expectedValue)) + expected[key] = actualValue + } + else if (isReplaceable(actualValue, expectedValue)) { + const replaced = replaceAsymmetricMatcher( + actualValue, + expectedValue, + actualReplaced, + expectedReplaced, + ) + actual[key] = replaced.replacedActual + expected[key] = replaced.replacedExpected + } + }) + return { + replacedActual: actual, + replacedExpected: expected, + } +} diff --git a/packages/runner/src/index.ts b/packages/runner/src/index.ts new file mode 100644 index 000000000000..2f98ab5f645e --- /dev/null +++ b/packages/runner/src/index.ts @@ -0,0 +1,3 @@ +export { startTests } from './run' +export { processError } from './error' +export * from './types' diff --git a/packages/runner/src/map.ts b/packages/runner/src/map.ts new file mode 100644 index 000000000000..6cf99cbd4643 --- /dev/null +++ b/packages/runner/src/map.ts @@ -0,0 +1,22 @@ +import type { Awaitable } from '@vitest/utils' +import type { Suite, SuiteHooks, Test } from './types' + +// use WeakMap here to make the Test and Suite object serializable +const fnMap = new WeakMap() +const hooksMap = new WeakMap() + +export function setFn(key: Test, fn: (() => Awaitable)) { + fnMap.set(key, fn) +} + +export function getFn(key: Task): (() => Awaitable) { + return fnMap.get(key as any) +} + +export function setHooks(key: Suite, hooks: SuiteHooks) { + hooksMap.set(key, hooks) +} + +export function getHooks(key: Suite): SuiteHooks { + return hooksMap.get(key) +} diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts new file mode 100644 index 000000000000..b3853c785615 --- /dev/null +++ b/packages/runner/src/run.ts @@ -0,0 +1,405 @@ +import limit from 'p-limit' +import { rpc, shuffle } from '@vitest/utils' +import type { VitestRunner } from './types/runner' +import type { File, HookCleanupCallback, HookListener, SequenceHooks, Suite, SuiteHooks, Task, TaskResult, TaskState, Test } from './types' +import { partitionSuiteChildren } from './utils/suite' +import { getFn, getHooks } from './map' +import { collectTests } from './collect' +import { processError } from './error' +import { setCurrentTest } from './test-state' +import { hasFailed, hasTests } from './utils/tasks' + +const now = Date.now + +function updateSuiteHookState(suite: Task, name: keyof SuiteHooks, state: TaskState) { + if (!suite.result) + suite.result = { state: 'run' } + if (!suite.result?.hooks) + suite.result.hooks = {} + const suiteHooks = suite.result.hooks + if (suiteHooks) { + suiteHooks[name] = state + updateTask(suite) + } +} + +function getSuiteHooks(suite: Suite, name: keyof SuiteHooks, sequence: SequenceHooks) { + const hooks = getHooks(suite)[name] + if (sequence === 'stack' && (name === 'afterAll' || name === 'afterEach')) + return hooks.slice().reverse() + return hooks +} + +export async function callSuiteHook( + suite: Suite, + currentTask: Task, + name: T, + sequence: SequenceHooks, + args: SuiteHooks[T][0] extends HookListener ? A : never, +): Promise { + const callbacks: HookCleanupCallback[] = [] + if (name === 'beforeEach' && suite.suite) { + callbacks.push( + ...await callSuiteHook(suite.suite, currentTask, name, sequence, args), + ) + } + + updateSuiteHookState(currentTask, name, 'run') + + const hooks = getSuiteHooks(suite, name, sequence) + + if (sequence === 'parallel') { + callbacks.push(...await Promise.all(hooks.map(fn => fn(...args as any)))) + } + else { + for (const hook of hooks) + callbacks.push(await hook(...args as any)) + } + + updateSuiteHookState(currentTask, name, 'pass') + + if (name === 'afterEach' && suite.suite) { + callbacks.push( + ...await callSuiteHook(suite.suite, currentTask, name, sequence, args), + ) + } + + return callbacks +} + +const packs = new Map() +let updateTimer: any +let previousUpdate: Promise | undefined + +function updateTask(task: Task) { + packs.set(task.id, task.result) + + clearTimeout(updateTimer) + updateTimer = setTimeout(() => { + previousUpdate = sendTasksUpdate() + }, 10) +} + +async function sendTasksUpdate() { + clearTimeout(updateTimer) + await previousUpdate + + if (packs.size) { + const p = rpc().onTaskUpdate(Array.from(packs)) + packs.clear() + return p + } +} + +const callCleanupHooks = async (cleanups: HookCleanupCallback[]) => { + await Promise.all(cleanups.map(async (fn) => { + if (typeof fn !== 'function') + return + await fn() + })) +} + +export async function runTest(test: Test, runner: VitestRunner) { + await runner.onBeforeRunTest?.(test) + + if (test.mode !== 'run') { + // TODO: move to hook + // const { getSnapshotClient } = await import('../integrations/snapshot/chai') + // getSnapshotClient().skipTestSnapshots(test) + return + } + + if (test.result?.state === 'fail') { + updateTask(test) + return + } + + const start = now() + + test.result = { + state: 'run', + startTime: start, + } + updateTask(test) + + // TODO: MOVE TO HOOK + // clearModuleMocks() + + setCurrentTest(test) + + // if (isNode) { + // const { getSnapshotClient } = await import('../integrations/snapshot/chai') + // await getSnapshotClient().setTest(test) + // } + + // const workerState = getWorkerState() + + // workerState.current = test + + const hooksSequence = runner.config.sequence.hooks + + const retry = test.retry || 1 + for (let retryCount = 0; retryCount < retry; retryCount++) { + let beforeEachCleanups: HookCleanupCallback[] = [] + try { + // const state: Partial = { + // assertionCalls: 0, + // isExpectingAssertions: false, + // isExpectingAssertionsError: null, + // expectedAssertionsNumber: null, + // expectedAssertionsNumberErrorGen: null, + // testPath: test.suite.file?.filepath, + // currentTestName: getFullName(test), + // // snapshotState: getSnapshotClient().snapshotState, + // } + // setState( + // runner.augmentExpectState?.(state) || state, + // (globalThis as any)[GLOBAL_EXPECT], + // ) + await runner.onBeforeTryTest?.(test, retryCount) + + beforeEachCleanups = await callSuiteHook(test.suite, test, 'beforeEach', hooksSequence, [test.context, test.suite]) + + test.result.retryCount = retryCount + + await getFn(test)() + + await runner.onAfterTryTest?.(test, retryCount) + // const { + // assertionCalls, + // expectedAssertionsNumber, + // expectedAssertionsNumberErrorGen, + // isExpectingAssertions, + // isExpectingAssertionsError, + // } = runner.receiveExpectState?.(test) || getState((globalThis as any)[GLOBAL_EXPECT]) + // if (expectedAssertionsNumber !== null && assertionCalls !== expectedAssertionsNumber) + // throw expectedAssertionsNumberErrorGen!() + // if (isExpectingAssertions === true && assertionCalls === 0) + // throw isExpectingAssertionsError + + test.result.state = 'pass' + } + catch (e) { + const error = processError(e) + test.result.state = 'fail' + test.result.error = error + test.result.errors = [error] + } + + try { + await callSuiteHook(test.suite, test, 'afterEach', hooksSequence, [test.context, test.suite]) + await callCleanupHooks(beforeEachCleanups) + } + catch (e) { + const error = processError(e) + test.result.state = 'fail' + test.result.error = error + test.result.errors = [error] + } + + if (test.result.state === 'pass') + break + + // update retry info + updateTask(test) + } + + if (test.result.state === 'fail') + await Promise.all(test.onFailed?.map(fn => fn(test.result!)) || []) + + // if test is marked to be failed, flip the result + if (test.fails) { + if (test.result.state === 'pass') { + const error = processError(new Error('Expect test to fail')) + test.result.state = 'fail' + test.result.error = error + test.result.errors = [error] + } + else { + test.result.state = 'pass' + test.result.error = undefined + test.result.errors = undefined + } + } + + setCurrentTest(undefined) + + test.result.duration = now() - start + + await runner.onAfterRunTest?.(test) + + // if (isNode) { + // const { getSnapshotClient } = await import('../integrations/snapshot/chai') + // getSnapshotClient().clearTest() + // } + + // if (workerState.config.logHeapUsage && isNode) + // test.result.heap = process.memoryUsage().heapUsed + + // workerState.current = undefined + + updateTask(test) +} + +function markTasksAsSkipped(suite: Suite) { + suite.tasks.forEach((t) => { + t.mode = 'skip' + t.result = { ...t.result, state: 'skip' } + updateTask(t) + if (t.type === 'suite') + markTasksAsSkipped(t) + }) +} + +export async function runSuite(suite: Suite, runner: VitestRunner) { + await runner.onBeforeRunSuite?.(suite) + + if (suite.result?.state === 'fail') { + markTasksAsSkipped(suite) + updateTask(suite) + return + } + + const start = now() + + suite.result = { + state: 'run', + startTime: start, + } + + updateTask(suite) + + const hooksSequence = runner.config.sequence.hooks + + // const workerState = getWorkerState() + + if (suite.mode === 'skip') { + suite.result.state = 'skip' + } + else if (suite.mode === 'todo') { + suite.result.state = 'todo' + } + else { + try { + const beforeAllCleanups = await callSuiteHook(suite, suite, 'beforeAll', hooksSequence, [suite]) + + for (let tasksGroup of partitionSuiteChildren(suite)) { + if (tasksGroup[0].concurrent === true) { + const mutex = limit(runner.config.maxConcurrency) + await Promise.all(tasksGroup.map(c => mutex(() => runSuiteChild(c, runner)))) + } + else { + const { sequence } = runner.config + if (sequence.shuffle || suite.shuffle) { + // run describe block independently from tests + const suites = tasksGroup.filter(group => group.type === 'suite') + const tests = tasksGroup.filter(group => group.type === 'test') + const groups = shuffle([suites, tests], sequence.seed) + tasksGroup = groups.flatMap(group => shuffle(group, sequence.seed)) + } + for (const c of tasksGroup) + await runSuiteChild(c, runner) + } + } + + await callSuiteHook(suite, suite, 'afterAll', hooksSequence, [suite]) + await callCleanupHooks(beforeAllCleanups) + } + catch (e) { + const error = processError(e) + suite.result.state = 'fail' + suite.result.error = error + suite.result.errors = [error] + } + } + suite.result.duration = now() - start + + await runner.onAfterRunSuite?.(suite) + + // if (workerState.config.logHeapUsage && isNode) + // suite.result.heap = process.memoryUsage().heapUsed + + if (suite.mode === 'run') { + if (!hasTests(suite)) { + suite.result.state = 'fail' + if (!suite.result.error) { + const error = processError(new Error(`No test found in suite ${suite.name}`)) + suite.result.error = error + suite.result.errors = [error] + } + } + else if (hasFailed(suite)) { + suite.result.state = 'fail' + } + else { + suite.result.state = 'pass' + } + } + + updateTask(suite) +} + +async function runSuiteChild(c: Task, runner: VitestRunner) { + if (c.type === 'test') + return runTest(c, runner) + + else if (c.type === 'suite') + return runSuite(c, runner) +} + +export async function runFiles(files: File[], runner: VitestRunner) { + for (const file of files) { + if (!file.tasks.length && !runner.config.passWithNoTests) { + if (!file.result?.errors?.length) { + const error = processError(new Error(`No test suite found in file ${file.filepath}`)) + file.result = { + state: 'fail', + error, + errors: [error], + } + } + } + await runSuite(file, runner) + } +} + +export async function startTests(paths: string[], runner: VitestRunner) { + await runner.onBeforeCollect?.() + + const files = await collectTests(paths, runner) + + runner.onCollected?.(files) + // rpc().onCollected(files) + + // const { getSnapshotClient } = await import('../integrations/snapshot/chai') + // getSnapshotClient().clear() + + await runFiles(files, runner) + + await runner.onAfterRun?.() + // const coverage = await takeCoverageInsideWorker(config.coverage) + // rpc().onAfterSuiteRun({ coverage }) + + // await getSnapshotClient().saveCurrent() + + await sendTasksUpdate() + + return files +} + +// export function clearModuleMocks() { +// const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals } = getWorkerState().config + +// // since each function calls another, we can just call one +// if (restoreMocks) +// vi.restoreAllMocks() +// else if (mockReset) +// vi.resetAllMocks() +// else if (clearMocks) +// vi.clearAllMocks() + +// if (unstubEnvs) +// vi.unstubAllEnvs() +// if (unstubGlobals) +// vi.unstubAllGlobals() +// } diff --git a/packages/runner/src/setup.ts b/packages/runner/src/setup.ts new file mode 100644 index 000000000000..0ec0872f1625 --- /dev/null +++ b/packages/runner/src/setup.ts @@ -0,0 +1,13 @@ +import { toArray } from '@vitest/utils' +import type { VitestRunner, VitestRunnerConfig } from './types' + +export async function runSetupFiles(config: VitestRunnerConfig, runner: VitestRunner) { + const files = toArray(config.setupFiles) + await Promise.all( + files.map(async (fsPath) => { + // TODO: check if it's a setup file and remove + // getWorkerState().moduleCache.delete(fsPath) + await runner.importFile(fsPath) + }), + ) +} diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts new file mode 100644 index 000000000000..0831a227c6ed --- /dev/null +++ b/packages/runner/src/suite.ts @@ -0,0 +1,251 @@ +import { format, isObject, noop, objDisplay, objectAttr } from '@vitest/utils' +import type { File, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Task, Test, TestAPI, TestFunction, TestOptions } from './types' +import type { VitestRunner } from './types/runner' +import { createChainable } from './chain' +import { collectTask, collectorContext, createTestContext, runWithSuite, withTimeout } from './context' +import { getHooks, setFn, setHooks } from './map' + +// apis +export const suite = createSuite() +export const test = createTest( + function (name: string, fn?: TestFunction, options?: number | TestOptions) { + getCurrentSuite().test.fn.call(this, name, fn, options) + }, +) + +// alias +export const describe = suite +export const it = test + +let runner: VitestRunner +let defaultSuite: SuiteCollector + +export function getDefaultSuite() { + return defaultSuite +} + +export function clearCollectorContext(currentRunner: VitestRunner) { + if (!defaultSuite) + defaultSuite = currentRunner.config.sequence.shuffle ? suite.shuffle('') : suite('') + runner = currentRunner + collectorContext.tasks.length = 0 + defaultSuite.clear() + collectorContext.currentSuite = defaultSuite +} + +export function getCurrentSuite() { + return (collectorContext.currentSuite || defaultSuite) as SuiteCollector +} + +export function createSuiteHooks() { + return { + beforeAll: [], + afterAll: [], + beforeEach: [], + afterEach: [], + } +} + +// implementations +function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, shuffle?: boolean, suiteOptions?: number | TestOptions) { + const tasks: (Test | Suite | SuiteCollector)[] = [] + const factoryQueue: (Test | Suite | SuiteCollector)[] = [] + + let suite: Suite + + initSuite() + + const test = createTest(function (name: string, fn = noop, options = suiteOptions) { + const mode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run' + + if (typeof options === 'number') + options = { timeout: options } + + const test: Test = { + id: '', + type: 'test', + name, + mode, + suite: undefined!, + fails: this.fails, + retry: options?.retry, + } as Omit as Test + + if (this.concurrent || concurrent) + test.concurrent = true + if (shuffle) + test.shuffle = true + + const context = createTestContext(test, runner) + // create test context + Object.defineProperty(test, 'context', { + value: context, + enumerable: false, + }) + + setFn(test, withTimeout( + () => fn(context), + options?.timeout ?? runner.config.testTimeout, + )) + + tasks.push(test) + }) + + const collector: SuiteCollector = { + type: 'collector', + name, + mode, + test, + tasks, + collect, + clear, + on: addHook, + } + + function addHook(name: T, ...fn: SuiteHooks[T]) { + getHooks(suite)[name].push(...fn as any) + } + + function initSuite() { + suite = { + id: '', + type: 'suite', + name, + mode, + shuffle, + tasks: [], + } + setHooks(suite, createSuiteHooks()) + } + + function clear() { + tasks.length = 0 + factoryQueue.length = 0 + initSuite() + } + + async function collect(file?: File) { + factoryQueue.length = 0 + if (factory) + await runWithSuite(collector, () => factory(test)) + + const allChildren: Task[] = [] + + for (const i of [...factoryQueue, ...tasks]) + allChildren.push(i.type === 'collector' ? await i.collect(file) : i) + + suite.file = file + suite.tasks = allChildren + + allChildren.forEach((task) => { + task.suite = suite + if (file) + task.file = file + }) + + return suite + } + + collectTask(collector) + return collector +} + +function createSuite() { + function suiteFn(this: Record, name: string, factory?: SuiteFactory, options?: number | TestOptions) { + const mode: RunMode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run' + return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options) + } + + suiteFn.each = function(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray, ...args: any[]) { + const suite = this.withContext() + + if (Array.isArray(cases) && args.length) + cases = formatTemplateString(cases, args) + + return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => { + const arrayOnlyCases = cases.every(Array.isArray) + cases.forEach((i, idx) => { + const items = Array.isArray(i) ? i : [i] + arrayOnlyCases + ? suite(formatTitle(name, items, idx), () => fn(...items), options) + : suite(formatTitle(name, items, idx), () => fn(i), options) + }) + } + } + + suiteFn.skipIf = (condition: any) => (condition ? suite.skip : suite) as SuiteAPI + suiteFn.runIf = (condition: any) => (condition ? suite : suite.skip) as SuiteAPI + + return createChainable( + ['concurrent', 'shuffle', 'skip', 'only', 'todo'], + suiteFn, + ) as unknown as SuiteAPI +} + +function createTest(fn: ( + ( + this: Record<'concurrent' | 'skip' | 'only' | 'todo' | 'fails', boolean | undefined>, + title: string, + fn?: TestFunction, + options?: number | TestOptions + ) => void +)) { + const testFn = fn as any + + testFn.each = function(this: { withContext: () => TestAPI }, cases: ReadonlyArray, ...args: any[]) { + const test = this.withContext() + + if (Array.isArray(cases) && args.length) + cases = formatTemplateString(cases, args) + + return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => { + const arrayOnlyCases = cases.every(Array.isArray) + cases.forEach((i, idx) => { + const items = Array.isArray(i) ? i : [i] + + arrayOnlyCases + ? test(formatTitle(name, items, idx), () => fn(...items), options) + : test(formatTitle(name, items, idx), () => fn(i), options) + }) + } + } + + testFn.skipIf = (condition: any) => (condition ? test.skip : test) as TestAPI + testFn.runIf = (condition: any) => (condition ? test : test.skip) as TestAPI + + return createChainable( + ['concurrent', 'skip', 'only', 'todo', 'fails'], + testFn, + ) as TestAPI +} + +function formatTitle(template: string, items: any[], idx: number) { + if (template.includes('%#')) { + // '%#' match index of the test case + template = template + .replace(/%%/g, '__vitest_escaped_%__') + .replace(/%#/g, `${idx}`) + .replace(/__vitest_escaped_%__/g, '%%') + } + const count = template.split('%').length - 1 + let formatted = format(template, ...items.slice(0, count)) + if (isObject(items[0])) { + formatted = formatted.replace(/\$([$\w_.]+)/g, + (_, key) => objDisplay(objectAttr(items[0], key)) as unknown as string, + // https://github.com/chaijs/chai/pull/1490 + ) + } + return formatted +} + +function formatTemplateString(cases: any[], args: any[]): any[] { + const header = cases.join('').trim().replace(/ /g, '').split('\n').map(i => i.split('|'))[0] + const res: any[] = [] + for (let i = 0; i < Math.floor((args.length) / header.length); i++) { + const oneCase: Record = {} + for (let j = 0; j < header.length; j++) + oneCase[header[j]] = args[i * header.length + j] as any + res.push(oneCase) + } + return res +} diff --git a/packages/runner/src/test-state.ts b/packages/runner/src/test-state.ts new file mode 100644 index 000000000000..1761e8130a96 --- /dev/null +++ b/packages/runner/src/test-state.ts @@ -0,0 +1,11 @@ +import type { Test } from './types' + +let _test: Test | undefined + +export function setCurrentTest(test: Test | undefined) { + _test = test +} + +export function getCurrentTest() { + return _test +} diff --git a/packages/runner/src/types/index.ts b/packages/runner/src/types/index.ts new file mode 100644 index 000000000000..01ac112a8a57 --- /dev/null +++ b/packages/runner/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './tasks' +export * from './runner' diff --git a/packages/runner/src/types/runner.ts b/packages/runner/src/types/runner.ts new file mode 100644 index 000000000000..8daed1e4bc0b --- /dev/null +++ b/packages/runner/src/types/runner.ts @@ -0,0 +1,36 @@ +import type { File, SequenceHooks, Suite, Test, TestContext } from './tasks' + +export interface VitestRunnerConfig { + root: string + setupFiles: string[] | string + name: string + passWithNoTests: boolean + testNamePattern?: string + allowOnly?: boolean + sequence: { + shuffle: boolean + seed: number + hooks: SequenceHooks + } + maxConcurrency: number + testTimeout: number + hookTimeout: number +} + +export interface VitestRunner { + onBeforeCollect?(): unknown + onCollected?(files: File[]): unknown + + onBeforeRunTest?(test: Test): unknown + onBeforeTryTest?(test: Test, retryCount: number): unknown + onAfterRunTest?(test: Test): unknown + onAfterTryTest?(test: Test, retryCount: number): unknown + + onBeforeRunSuite?(suite: Suite): unknown + onAfterRunSuite?(suite: Suite): unknown + + onAfterRun?(): unknown + importFile(filepath: string): unknown + augmentTestContext?(context: TestContext): TestContext + config: VitestRunnerConfig +} diff --git a/packages/runner/src/types/tasks.ts b/packages/runner/src/types/tasks.ts new file mode 100644 index 000000000000..d8ca45af3caf --- /dev/null +++ b/packages/runner/src/types/tasks.ts @@ -0,0 +1,235 @@ +import type { Awaitable } from '@vitest/utils' +import type { ChainableFunction } from '../chain' +import type { ErrorWithDiff } from '../error' + +export type RunMode = 'run' | 'skip' | 'only' | 'todo' +export type TaskState = RunMode | 'pass' | 'fail' + +export interface TaskBase { + id: string + name: string + mode: RunMode + concurrent?: boolean + shuffle?: boolean + suite?: Suite + file?: File + result?: TaskResult + retry?: number + // TODO: Augment in "vitest" + // logs?: UserConsoleLog[] + meta?: any +} + +export interface TaskResult { + state: TaskState + duration?: number + startTime?: number + heap?: number + /** + * @deprecated Use "errors" instead + */ + error?: ErrorWithDiff + errors?: ErrorWithDiff[] + htmlError?: string + hooks?: Partial> + retryCount?: number +} + +export type TaskResultPack = [id: string, result: TaskResult | undefined] + +export interface Suite extends TaskBase { + type: 'suite' + tasks: Task[] + filepath?: string + projectName?: string +} + +export interface File extends Suite { + filepath: string + collectDuration?: number + setupDuration?: number +} + +export interface Test extends TaskBase { + type: 'test' + suite: Suite + result?: TaskResult + fails?: boolean + context: TestContext & ExtraContext + onFailed?: OnTestFailedHandler[] +} + +export type Task = Test | Suite | File + +export type DoneCallback = (error?: any) => void +export type TestFunction = (context: TestContext & ExtraContext) => Awaitable | void + +// jest's ExtractEachCallbackArgs +type ExtractEachCallbackArgs> = { + 1: [T[0]] + 2: [T[0], T[1]] + 3: [T[0], T[1], T[2]] + 4: [T[0], T[1], T[2], T[3]] + 5: [T[0], T[1], T[2], T[3], T[4]] + 6: [T[0], T[1], T[2], T[3], T[4], T[5]] + 7: [T[0], T[1], T[2], T[3], T[4], T[5], T[6]] + 8: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7]] + 9: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], T[8]] + 10: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], T[8], T[9]] + fallback: Array ? U : any> +}[T extends Readonly<[any]> + ? 1 + : T extends Readonly<[any, any]> + ? 2 + : T extends Readonly<[any, any, any]> + ? 3 + : T extends Readonly<[any, any, any, any]> + ? 4 + : T extends Readonly<[any, any, any, any, any]> + ? 5 + : T extends Readonly<[any, any, any, any, any, any]> + ? 6 + : T extends Readonly<[any, any, any, any, any, any, any]> + ? 7 + : T extends Readonly<[any, any, any, any, any, any, any, any]> + ? 8 + : T extends Readonly<[any, any, any, any, any, any, any, any, any]> + ? 9 + : T extends Readonly<[any, any, any, any, any, any, any, any, any, any]> + ? 10 + : 'fallback'] + +interface SuiteEachFunction { + (cases: ReadonlyArray): ( + name: string, + fn: (...args: T) => Awaitable, + ) => void + >(cases: ReadonlyArray): ( + name: string, + fn: (...args: ExtractEachCallbackArgs) => Awaitable, + ) => void + (cases: ReadonlyArray): ( + name: string, + fn: (...args: T[]) => Awaitable, + ) => void +} + +interface TestEachFunction { + (cases: ReadonlyArray): ( + name: string, + fn: (...args: T) => Awaitable, + options?: number | TestOptions, + ) => void + >(cases: ReadonlyArray): ( + name: string, + fn: (...args: ExtractEachCallbackArgs) => Awaitable, + options?: number | TestOptions, + ) => void + (cases: ReadonlyArray): ( + name: string, + fn: (...args: T[]) => Awaitable, + options?: number | TestOptions, + ) => void + (...args: [TemplateStringsArray, ...any]): ( + name: string, + fn: (...args: any[]) => Awaitable, + options?: number | TestOptions, + ) => void +} + +type ChainableTestAPI = ChainableFunction< + 'concurrent' | 'only' | 'skip' | 'todo' | 'fails', + [name: string, fn?: TestFunction, options?: number | TestOptions], + void, + { + each: TestEachFunction + (name: string, fn?: TestFunction, options?: number | TestOptions): void + } +> + +export interface TestOptions { + /** + * Test timeout. + */ + timeout?: number + /** + * Times to retry the test if fails. Useful for making flaky tests more stable. + * When retries is up, the last test error will be thrown. + * + * @default 1 + */ + retry?: number +} + +export type TestAPI = ChainableTestAPI & { + each: TestEachFunction + skipIf(condition: any): ChainableTestAPI + runIf(condition: any): ChainableTestAPI +} + +type ChainableSuiteAPI = ChainableFunction< + 'concurrent' | 'only' | 'skip' | 'todo' | 'shuffle', + [name: string, factory?: SuiteFactory, options?: number | TestOptions], + SuiteCollector, + { + each: TestEachFunction + (name: string, factory?: SuiteFactory): SuiteCollector + } +> + +export type SuiteAPI = ChainableSuiteAPI & { + each: SuiteEachFunction + skipIf(condition: any): ChainableSuiteAPI + runIf(condition: any): ChainableSuiteAPI +} + +export type HookListener = (...args: T) => Awaitable + +export type HookCleanupCallback = (() => Awaitable) | void + +export interface SuiteHooks { + beforeAll: HookListener<[Suite | File], HookCleanupCallback>[] + afterAll: HookListener<[Suite | File]>[] + beforeEach: HookListener<[TestContext & ExtraContext, Suite], HookCleanupCallback>[] + afterEach: HookListener<[TestContext & ExtraContext, Suite]>[] +} + +export interface SuiteCollector { + readonly name: string + readonly mode: RunMode + type: 'collector' + test: TestAPI + tasks: (Suite | Test | SuiteCollector)[] + collect: (file?: File) => Promise + clear: () => void + on: >(name: T, ...fn: SuiteHooks[T]) => void +} + +export type SuiteFactory = (test: (name: string, fn: TestFunction) => void) => Awaitable + +export interface RuntimeContext { + tasks: (SuiteCollector | Test)[] + currentSuite: SuiteCollector | null +} + +export interface TestContext { + /** + * Metadata of the current test + */ + meta: Readonly + + // TODO: augment by "vitest" + /** + * A expect instance bound to the test + */ + // expect: Vi.ExpectStatic + + /** + * Extract hooks on test failed + */ + onTestFailed: (fn: OnTestFailedHandler) => void +} + +export type OnTestFailedHandler = (result: TaskResult) => Awaitable + +export type SequenceHooks = 'stack' | 'list' | 'parallel' diff --git a/packages/runner/src/utils/collect.ts b/packages/runner/src/utils/collect.ts new file mode 100644 index 000000000000..753797b0a0b2 --- /dev/null +++ b/packages/runner/src/utils/collect.ts @@ -0,0 +1,94 @@ +import type { Suite, TaskBase } from '../types' + +/** + * If any tasks been marked as `only`, mark all other tasks as `skip`. + */ +export function interpretTaskModes(suite: Suite, namePattern?: string | RegExp, onlyMode?: boolean, parentIsOnly?: boolean, allowOnly?: boolean) { + const suiteIsOnly = parentIsOnly || suite.mode === 'only' + + suite.tasks.forEach((t) => { + // Check if either the parent suite or the task itself are marked as included + const includeTask = suiteIsOnly || t.mode === 'only' + if (onlyMode) { + if (t.type === 'suite' && (includeTask || someTasksAreOnly(t))) { + // Don't skip this suite + if (t.mode === 'only') { + checkAllowOnly(t, allowOnly) + t.mode = 'run' + } + } + else if (t.mode === 'run' && !includeTask) { + t.mode = 'skip' + } + else if (t.mode === 'only') { + checkAllowOnly(t, allowOnly) + t.mode = 'run' + } + } + if (t.type === 'test') { + if (namePattern && !getTaskFullName(t).match(namePattern)) + t.mode = 'skip' + } + else if (t.type === 'suite') { + if (t.mode === 'skip') + skipAllTasks(t) + else + interpretTaskModes(t, namePattern, onlyMode, includeTask, allowOnly) + } + }) + + // if all subtasks are skipped, mark as skip + if (suite.mode === 'run') { + if (suite.tasks.length && suite.tasks.every(i => i.mode !== 'run')) + suite.mode = 'skip' + } +} + +function getTaskFullName(task: TaskBase): string { + return `${task.suite ? `${getTaskFullName(task.suite)} ` : ''}${task.name}` +} + +export function someTasksAreOnly(suite: Suite): boolean { + return suite.tasks.some(t => t.mode === 'only' || (t.type === 'suite' && someTasksAreOnly(t))) +} + +function skipAllTasks(suite: Suite) { + suite.tasks.forEach((t) => { + if (t.mode === 'run') { + t.mode = 'skip' + if (t.type === 'suite') + skipAllTasks(t) + } + }) +} + +function checkAllowOnly(task: TaskBase, allowOnly?: boolean) { + if (allowOnly) + return + const error = new Error('[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error') + task.result = { + state: 'fail', + error, + errors: [error], + } +} + +export function generateHash(str: string): string { + let hash = 0 + if (str.length === 0) + return `${hash}` + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i) + hash = (hash << 5) - hash + char + hash = hash & hash // Convert to 32bit integer + } + return `${hash}` +} + +export function calculateSuiteHash(parent: Suite) { + parent.tasks.forEach((t, idx) => { + t.id = `${parent.id}_${idx}` + if (t.type === 'suite') + calculateSuiteHash(t) + }) +} diff --git a/packages/runner/src/utils/index.ts b/packages/runner/src/utils/index.ts new file mode 100644 index 000000000000..1d2a471c247e --- /dev/null +++ b/packages/runner/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './collect' +export * from './suite' +export * from './tasks' diff --git a/packages/runner/src/utils/suite.ts b/packages/runner/src/utils/suite.ts new file mode 100644 index 000000000000..c60a71aaaa64 --- /dev/null +++ b/packages/runner/src/utils/suite.ts @@ -0,0 +1,22 @@ +import type { Suite, Task } from '../types' + +/** + * Partition in tasks groups by consecutive concurrent + */ +export function partitionSuiteChildren(suite: Suite) { + let tasksGroup: Task[] = [] + const tasksGroups: Task[][] = [] + for (const c of suite.tasks) { + if (tasksGroup.length === 0 || c.concurrent === tasksGroup[0].concurrent) { + tasksGroup.push(c) + } + else { + tasksGroups.push(tasksGroup) + tasksGroup = [c] + } + } + if (tasksGroup.length > 0) + tasksGroups.push(tasksGroup) + + return tasksGroups +} diff --git a/packages/runner/src/utils/tasks.ts b/packages/runner/src/utils/tasks.ts new file mode 100644 index 000000000000..fde9229dbe1a --- /dev/null +++ b/packages/runner/src/utils/tasks.ts @@ -0,0 +1,45 @@ +import { type Arrayable, toArray } from '@vitest/utils' +import type { Suite, Task, Test } from '../types' + +function isAtomTest(s: Task): s is Test { + return (s.type === 'test') +} + +export function getTests(suite: Arrayable): (Test)[] { + return toArray(suite).flatMap(s => isAtomTest(s) ? [s] : s.tasks.flatMap(c => isAtomTest(c) ? [c] : getTests(c))) +} + +export function getTasks(tasks: Arrayable = []): Task[] { + return toArray(tasks).flatMap(s => isAtomTest(s) ? [s] : [s, ...getTasks(s.tasks)]) +} + +export function getSuites(suite: Arrayable): Suite[] { + return toArray(suite).flatMap(s => s.type === 'suite' ? [s, ...getSuites(s.tasks)] : []) +} + +export function hasTests(suite: Arrayable): boolean { + return toArray(suite).some(s => s.tasks.some(c => isAtomTest(c) || hasTests(c))) +} + +export function hasFailed(suite: Arrayable): boolean { + return toArray(suite).some(s => s.result?.state === 'fail' || (s.type === 'suite' && hasFailed(s.tasks))) +} + +export function hasFailedSnapshot(suite: Arrayable): boolean { + return getTests(suite).some((s) => { + return s.result?.errors?.some(e => e.message.match(/Snapshot .* mismatched/)) + }) +} + +export function getNames(task: Task) { + const names = [task.name] + let current: Task | undefined = task + + while (current?.suite || current?.file) { + current = current.suite || current.file + if (current?.name) + names.unshift(current.name) + } + + return names +} diff --git a/packages/runner/utils.d.ts b/packages/runner/utils.d.ts new file mode 100644 index 000000000000..e3f344e48a8d --- /dev/null +++ b/packages/runner/utils.d.ts @@ -0,0 +1 @@ +export * from './dist/utils.js' From 730d7d68db34c4f1c84b939d3c2d8a3df53eab9b Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 12:08:06 +0100 Subject: [PATCH 02/47] fix: don't use rpc in runner --- packages/runner/package.json | 1 - packages/runner/src/run.ts | 62 ++++++++++++++--------------- packages/runner/src/types/runner.ts | 4 +- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/packages/runner/package.json b/packages/runner/package.json index fee8a8ee37bf..a15261aedcad 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -35,7 +35,6 @@ }, "dependencies": { "@vitest/utils": "workspace:*", - "chai": "^4.3.7", "p-limit": "^4.0.0" } } diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index b3853c785615..e20b5263ad3b 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -1,5 +1,5 @@ import limit from 'p-limit' -import { rpc, shuffle } from '@vitest/utils' +import { shuffle } from '@vitest/utils' import type { VitestRunner } from './types/runner' import type { File, HookCleanupCallback, HookListener, SequenceHooks, Suite, SuiteHooks, Task, TaskResult, TaskState, Test } from './types' import { partitionSuiteChildren } from './utils/suite' @@ -11,7 +11,7 @@ import { hasFailed, hasTests } from './utils/tasks' const now = Date.now -function updateSuiteHookState(suite: Task, name: keyof SuiteHooks, state: TaskState) { +function updateSuiteHookState(suite: Task, name: keyof SuiteHooks, state: TaskState, runner: VitestRunner) { if (!suite.result) suite.result = { state: 'run' } if (!suite.result?.hooks) @@ -19,7 +19,7 @@ function updateSuiteHookState(suite: Task, name: keyof SuiteHooks, state: TaskSt const suiteHooks = suite.result.hooks if (suiteHooks) { suiteHooks[name] = state - updateTask(suite) + updateTask(suite, runner) } } @@ -34,17 +34,19 @@ export async function callSuiteHook( suite: Suite, currentTask: Task, name: T, - sequence: SequenceHooks, + runner: VitestRunner, args: SuiteHooks[T][0] extends HookListener ? A : never, ): Promise { + const sequence = runner.config.sequence.hooks + const callbacks: HookCleanupCallback[] = [] if (name === 'beforeEach' && suite.suite) { callbacks.push( - ...await callSuiteHook(suite.suite, currentTask, name, sequence, args), + ...await callSuiteHook(suite.suite, currentTask, name, runner, args), ) } - updateSuiteHookState(currentTask, name, 'run') + updateSuiteHookState(currentTask, name, 'run', runner) const hooks = getSuiteHooks(suite, name, sequence) @@ -56,11 +58,11 @@ export async function callSuiteHook( callbacks.push(await hook(...args as any)) } - updateSuiteHookState(currentTask, name, 'pass') + updateSuiteHookState(currentTask, name, 'pass', runner) if (name === 'afterEach' && suite.suite) { callbacks.push( - ...await callSuiteHook(suite.suite, currentTask, name, sequence, args), + ...await callSuiteHook(suite.suite, currentTask, name, runner, args), ) } @@ -71,21 +73,21 @@ const packs = new Map() let updateTimer: any let previousUpdate: Promise | undefined -function updateTask(task: Task) { +function updateTask(task: Task, runner: VitestRunner) { packs.set(task.id, task.result) clearTimeout(updateTimer) updateTimer = setTimeout(() => { - previousUpdate = sendTasksUpdate() + previousUpdate = sendTasksUpdate(runner) }, 10) } -async function sendTasksUpdate() { +async function sendTasksUpdate(runner: VitestRunner) { clearTimeout(updateTimer) await previousUpdate if (packs.size) { - const p = rpc().onTaskUpdate(Array.from(packs)) + const p = runner.onTaskUpdate?.(Array.from(packs)) packs.clear() return p } @@ -110,7 +112,7 @@ export async function runTest(test: Test, runner: VitestRunner) { } if (test.result?.state === 'fail') { - updateTask(test) + updateTask(test, runner) return } @@ -120,7 +122,7 @@ export async function runTest(test: Test, runner: VitestRunner) { state: 'run', startTime: start, } - updateTask(test) + updateTask(test, runner) // TODO: MOVE TO HOOK // clearModuleMocks() @@ -136,8 +138,6 @@ export async function runTest(test: Test, runner: VitestRunner) { // workerState.current = test - const hooksSequence = runner.config.sequence.hooks - const retry = test.retry || 1 for (let retryCount = 0; retryCount < retry; retryCount++) { let beforeEachCleanups: HookCleanupCallback[] = [] @@ -158,7 +158,7 @@ export async function runTest(test: Test, runner: VitestRunner) { // ) await runner.onBeforeTryTest?.(test, retryCount) - beforeEachCleanups = await callSuiteHook(test.suite, test, 'beforeEach', hooksSequence, [test.context, test.suite]) + beforeEachCleanups = await callSuiteHook(test.suite, test, 'beforeEach', runner, [test.context, test.suite]) test.result.retryCount = retryCount @@ -187,7 +187,7 @@ export async function runTest(test: Test, runner: VitestRunner) { } try { - await callSuiteHook(test.suite, test, 'afterEach', hooksSequence, [test.context, test.suite]) + await callSuiteHook(test.suite, test, 'afterEach', runner, [test.context, test.suite]) await callCleanupHooks(beforeEachCleanups) } catch (e) { @@ -201,7 +201,7 @@ export async function runTest(test: Test, runner: VitestRunner) { break // update retry info - updateTask(test) + updateTask(test, runner) } if (test.result.state === 'fail') @@ -238,16 +238,16 @@ export async function runTest(test: Test, runner: VitestRunner) { // workerState.current = undefined - updateTask(test) + updateTask(test, runner) } -function markTasksAsSkipped(suite: Suite) { +function markTasksAsSkipped(suite: Suite, runner: VitestRunner) { suite.tasks.forEach((t) => { t.mode = 'skip' t.result = { ...t.result, state: 'skip' } - updateTask(t) + updateTask(t, runner) if (t.type === 'suite') - markTasksAsSkipped(t) + markTasksAsSkipped(t, runner) }) } @@ -255,8 +255,8 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { await runner.onBeforeRunSuite?.(suite) if (suite.result?.state === 'fail') { - markTasksAsSkipped(suite) - updateTask(suite) + markTasksAsSkipped(suite, runner) + updateTask(suite, runner) return } @@ -267,9 +267,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { startTime: start, } - updateTask(suite) - - const hooksSequence = runner.config.sequence.hooks + updateTask(suite, runner) // const workerState = getWorkerState() @@ -281,7 +279,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { } else { try { - const beforeAllCleanups = await callSuiteHook(suite, suite, 'beforeAll', hooksSequence, [suite]) + const beforeAllCleanups = await callSuiteHook(suite, suite, 'beforeAll', runner, [suite]) for (let tasksGroup of partitionSuiteChildren(suite)) { if (tasksGroup[0].concurrent === true) { @@ -302,7 +300,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { } } - await callSuiteHook(suite, suite, 'afterAll', hooksSequence, [suite]) + await callSuiteHook(suite, suite, 'afterAll', runner, [suite]) await callCleanupHooks(beforeAllCleanups) } catch (e) { @@ -336,7 +334,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { } } - updateTask(suite) + updateTask(suite, runner) } async function runSuiteChild(c: Task, runner: VitestRunner) { @@ -382,7 +380,7 @@ export async function startTests(paths: string[], runner: VitestRunner) { // await getSnapshotClient().saveCurrent() - await sendTasksUpdate() + await sendTasksUpdate(runner) return files } diff --git a/packages/runner/src/types/runner.ts b/packages/runner/src/types/runner.ts index 8daed1e4bc0b..231c86398204 100644 --- a/packages/runner/src/types/runner.ts +++ b/packages/runner/src/types/runner.ts @@ -1,4 +1,4 @@ -import type { File, SequenceHooks, Suite, Test, TestContext } from './tasks' +import type { File, SequenceHooks, Suite, TaskResult, Test, TestContext } from './tasks' export interface VitestRunnerConfig { root: string @@ -29,6 +29,8 @@ export interface VitestRunner { onBeforeRunSuite?(suite: Suite): unknown onAfterRunSuite?(suite: Suite): unknown + onTaskUpdate?(task: [string, TaskResult | undefined][]): Promise + onAfterRun?(): unknown importFile(filepath: string): unknown augmentTestContext?(context: TestContext): TestContext From 386ec78e26ac165669915851b7033c79e1bb640e Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 12:08:28 +0100 Subject: [PATCH 03/47] chore: add more utils to vitest/utils --- packages/utils/package.json | 4 +- packages/utils/src/display.ts | 44 +++++++++++++++++++ packages/utils/src/helpers.ts | 82 +++++++++++++++++++++++++++++++++++ packages/utils/src/index.ts | 3 ++ packages/utils/src/random.ts | 21 +++++++++ packages/utils/src/timers.ts | 13 ++++++ pnpm-lock.yaml | 69 ++++++++++------------------- 7 files changed, 188 insertions(+), 48 deletions(-) create mode 100644 packages/utils/src/display.ts create mode 100644 packages/utils/src/random.ts create mode 100644 packages/utils/src/timers.ts diff --git a/packages/utils/package.json b/packages/utils/package.json index a19465973382..0a5ae89a6669 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -39,8 +39,10 @@ "dependencies": { "cli-truncate": "^3.1.0", "diff": "^5.1.0", + "loupe": "^2.3.6", "picocolors": "^1.0.0", - "pretty-format": "^27.5.1" + "pretty-format": "^27.5.1", + "util": "^0.12.5" }, "devDependencies": { "@types/diff": "^5.0.2" diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts new file mode 100644 index 000000000000..5dbd49a0a6cd --- /dev/null +++ b/packages/utils/src/display.ts @@ -0,0 +1,44 @@ +import util from 'util' +// @ts-expect-error doesn't have types +import loupe from 'loupe' + +export function format(...args: any[]) { + return util.format(...args) +} + +// chai utils +export function inspect(obj: unknown): string { + return loupe(obj, { + depth: 2, + truncate: 40, + }) +} + +export function objDisplay(obj: unknown) { + const truncateThreshold = 40 + const str = inspect(obj) + const type = Object.prototype.toString.call(obj) + + if (str.length >= truncateThreshold) { + if (type === '[object Function]') { + const fn = obj as () => void + return !fn.name || fn.name === '' + ? '[Function]' + : `[Function: ${fn.name}]` + } + else if (type === '[object Array]') { + return `[ Array(${(obj as []).length}) ]` + } + else if (type === '[object Object]') { + const keys = Object.keys(obj as {}) + const kstr = keys.length > 2 + ? `${keys.splice(0, 2).join(', ')}, ...` + : keys.join(', ') + return `{ Object (${kstr}) }` + } + else { + return str + } + } + return str +} diff --git a/packages/utils/src/helpers.ts b/packages/utils/src/helpers.ts index f2f963ebaae9..6aa0bff7c37e 100644 --- a/packages/utils/src/helpers.ts +++ b/packages/utils/src/helpers.ts @@ -1,3 +1,5 @@ +import type { Arrayable, Nullable } from './types' + export function assertTypes(value: unknown, name: string, types: string[]): void { const receivedType = typeof value const pass = types.includes(receivedType) @@ -5,6 +7,86 @@ export function assertTypes(value: unknown, name: string, types: string[]): void throw new TypeError(`${name} value must be ${types.join(' or ')}, received "${receivedType}"`) } +export function slash(path: string) { + return path.replace(/\\/g, '/') +} + +export function toArray(array?: Nullable>): Array { + if (array === null || array === undefined) + array = [] + + if (Array.isArray(array)) + return array + + return [array] +} + export function isObject(item: unknown): boolean { return item != null && typeof item === 'object' && !Array.isArray(item) } + +function isFinalObj(obj: any) { + return obj === Object.prototype || obj === Function.prototype || obj === RegExp.prototype +} + +export function getType(value: unknown): string { + return Object.prototype.toString.apply(value).slice(8, -1) +} + +function collectOwnProperties(obj: any, collector: Set | ((key: string | symbol) => void)) { + const collect = typeof collector === 'function' ? collector : (key: string | symbol) => collector.add(key) + Object.getOwnPropertyNames(obj).forEach(collect) + Object.getOwnPropertySymbols(obj).forEach(collect) +} + +export function getOwnProperties(obj: any) { + const ownProps = new Set() + if (isFinalObj(obj)) + return [] + collectOwnProperties(obj, ownProps) + return Array.from(ownProps) +} + +export function deepClone(val: T): T { + const seen = new WeakMap() + return clone(val, seen) +} + +export function clone(val: T, seen: WeakMap): T { + let k: any, out: any + if (seen.has(val)) + return seen.get(val) + if (Array.isArray(val)) { + out = Array(k = val.length) + seen.set(val, out) + while (k--) + out[k] = clone(val[k], seen) + return out as any + } + + if (Object.prototype.toString.call(val) === '[object Object]') { + out = Object.create(Object.getPrototypeOf(val)) + seen.set(val, out) + // we don't need properties from prototype + const props = getOwnProperties(val) + for (const k of props) + out[k] = clone((val as any)[k], seen) + return out + } + + return val +} + +export function noop() {} + +export function objectAttr(source: any, path: string, defaultValue = undefined) { + // a[3].b -> a.3.b + const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.') + let result = source + for (const p of paths) { + result = Object(result)[p] + if (result === undefined) + return defaultValue + } + return result +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index db9ed73002ee..0721002b7842 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -2,3 +2,6 @@ export * from './diff' export * from './helpers' export * from './types' export * from './stringify' +export * from './timers' +export * from './random' +export * from './display' diff --git a/packages/utils/src/random.ts b/packages/utils/src/random.ts new file mode 100644 index 000000000000..7cd33217275c --- /dev/null +++ b/packages/utils/src/random.ts @@ -0,0 +1,21 @@ +const RealDate = Date + +function random(seed: number) { + const x = Math.sin(seed++) * 10000 + return x - Math.floor(x) +} + +export function shuffle(array: T[], seed = RealDate.now()): T[] { + let length = array.length + + while (length) { + const index = Math.floor(random(seed) * length--) + + const previous = array[length] + array[length] = array[index] + array[index] = previous + ++seed + } + + return array +} diff --git a/packages/utils/src/timers.ts b/packages/utils/src/timers.ts new file mode 100644 index 000000000000..55b0fd03579c --- /dev/null +++ b/packages/utils/src/timers.ts @@ -0,0 +1,13 @@ +const { + setTimeout: safeSetTimeout, + setInterval: safeSetInterval, + clearInterval: safeClearInterval, + clearTimeout: safeClearTimeout, +} = globalThis + +export { + safeSetTimeout as setTimeout, + safeSetInterval as setInterval, + safeClearInterval as clearInterval, + safeClearTimeout as clearTimeout, +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c160b72af4b4..05040a2eb7fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,7 +294,7 @@ importers: '@types/react-test-renderer': 17.0.2 '@vitejs/plugin-react': 3.0.1_vite@4.0.0 '@vitest/ui': link:../../packages/ui - happy-dom: 8.1.4 + happy-dom: 8.1.5 jsdom: 21.0.0 react-test-renderer: 17.0.2_react@17.0.2 vite: 4.0.0 @@ -702,6 +702,14 @@ importers: chai: 4.3.7 picocolors: 1.0.0 + packages/runner: + specifiers: + '@vitest/utils': workspace:* + p-limit: ^4.0.0 + dependencies: + '@vitest/utils': link:../utils + p-limit: 4.0.0 + packages/spy: specifiers: tinyspy: ^1.0.2 @@ -782,13 +790,17 @@ importers: '@types/diff': ^5.0.2 cli-truncate: ^3.1.0 diff: ^5.1.0 + loupe: ^2.3.6 picocolors: ^1.0.0 pretty-format: ^27.5.1 + util: ^0.12.5 dependencies: cli-truncate: 3.1.0 diff: 5.1.0 + loupe: 2.3.6 picocolors: 1.0.0 pretty-format: 27.5.1 + util: 0.12.5 devDependencies: '@types/diff': 5.0.2 @@ -1043,7 +1055,7 @@ importers: devDependencies: '@vitejs/plugin-vue': 4.0.0_vite@4.0.0+vue@3.2.45 '@vue/test-utils': 2.2.7_vue@3.2.45 - happy-dom: 8.1.4 + happy-dom: 8.1.5 vite: 4.0.0 vitest: link:../../packages/vitest vue: 3.2.45 @@ -9501,7 +9513,6 @@ packages: /available-typed-arrays/1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - dev: true /avvio/8.2.0: resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==} @@ -10161,7 +10172,6 @@ packages: dependencies: function-bind: 1.1.1 get-intrinsic: 1.1.3 - dev: true /call-me-maybe/1.0.1: resolution: {integrity: sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==} @@ -11381,7 +11391,6 @@ packages: dependencies: has-property-descriptors: 1.0.0 object-keys: 1.1.1 - dev: true /define-property/0.2.5: resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} @@ -11850,7 +11859,6 @@ packages: string.prototype.trimend: 1.0.5 string.prototype.trimstart: 1.0.5 unbox-primitive: 1.0.2 - dev: true /es-array-method-boxes-properly/1.0.0: resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} @@ -11890,7 +11898,6 @@ packages: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 - dev: true /es5-shim/4.6.7: resolution: {integrity: sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==} @@ -13026,7 +13033,6 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - dev: true /for-in/1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} @@ -13238,7 +13244,6 @@ packages: define-properties: 1.1.4 es-abstract: 1.20.4 functions-have-names: 1.2.3 - dev: true /functional-red-black-tree/1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} @@ -13246,7 +13251,6 @@ packages: /functions-have-names/1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true /gauge/3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} @@ -13281,7 +13285,6 @@ packages: function-bind: 1.1.1 has: 1.0.3 has-symbols: 1.0.3 - dev: true /get-own-enumerable-property-symbols/3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} @@ -13328,7 +13331,6 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.1.3 - dev: true /get-tsconfig/4.3.0: resolution: {integrity: sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ==} @@ -13571,8 +13573,8 @@ packages: - encoding dev: true - /happy-dom/8.1.4: - resolution: {integrity: sha512-mUCzXHhSO6fOQlZwKW6z2f/+rYavKNxNrgY4nJ4dp+r8gTGbTENgMZGfM6eJD0DJPRFF8DFyngXdBF93wF96UA==} + /happy-dom/8.1.5: + resolution: {integrity: sha512-/UXAJ2fHTs4H3vy7TS7c9PKFvPyaNialk2Er9NdXfpBKNaCITMOH03rkjHXp5jnJnSmRBa+av8E08PUAaIB1jQ==} dependencies: css.escape: 1.5.1 he: 1.2.0 @@ -13593,7 +13595,6 @@ packages: /has-bigints/1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true /has-flag/3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -13614,19 +13615,16 @@ packages: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.1.3 - dev: true /has-symbols/1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag/1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: true /has-unicode/2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} @@ -14103,7 +14101,6 @@ packages: get-intrinsic: 1.1.3 has: 1.0.3 side-channel: 1.0.4 - dev: true /internmap/2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} @@ -14160,7 +14157,6 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 - dev: true /is-arrayish/0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -14169,7 +14165,6 @@ packages: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.2 - dev: true /is-binary-path/1.0.1: resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} @@ -14192,7 +14187,6 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 - dev: true /is-buffer/1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -14218,7 +14212,6 @@ packages: /is-callable/1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: true /is-ci/2.0.0: resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} @@ -14258,7 +14251,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-decimal/1.0.4: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} @@ -14334,7 +14326,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-glob/3.1.0: resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} @@ -14377,7 +14368,6 @@ packages: /is-negative-zero/2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} - dev: true /is-node-process/1.0.1: resolution: {integrity: sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==} @@ -14388,7 +14378,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-number/3.0.0: resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} @@ -14448,7 +14437,6 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 - dev: true /is-regexp/1.0.0: resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} @@ -14463,7 +14451,6 @@ packages: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: call-bind: 1.0.2 - dev: true /is-stream/1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} @@ -14485,7 +14472,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-subset/0.1.1: resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} @@ -14496,7 +14482,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: true /is-typed-array/1.1.9: resolution: {integrity: sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==} @@ -14507,7 +14492,6 @@ packages: es-abstract: 1.20.4 for-each: 0.3.3 has-tostringtag: 1.0.0 - dev: true /is-typedarray/1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -14531,7 +14515,6 @@ packages: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 - dev: true /is-weakset/2.0.2: resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} @@ -15456,6 +15439,12 @@ packages: get-func-name: 2.0.0 dev: false + /loupe/2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + dependencies: + get-func-name: 2.0.0 + dev: false + /lower-case/2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -16395,7 +16384,6 @@ packages: /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - dev: true /object-is/1.1.5: resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} @@ -16408,7 +16396,6 @@ packages: /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - dev: true /object-visit/1.0.1: resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} @@ -16425,7 +16412,6 @@ packages: define-properties: 1.1.4 has-symbols: 1.0.3 object-keys: 1.1.1 - dev: true /object.entries/1.1.5: resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==} @@ -16666,7 +16652,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.0.0 - dev: true /p-locate/3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} @@ -18079,7 +18064,6 @@ packages: call-bind: 1.0.2 define-properties: 1.1.4 functions-have-names: 1.2.3 - dev: true /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} @@ -18466,7 +18450,6 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.1.3 is-regex: 1.1.4 - dev: true /safe-regex/1.1.0: resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} @@ -18762,7 +18745,6 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.1.3 object-inspect: 1.12.2 - dev: true /siginfo/2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -19234,7 +19216,6 @@ packages: call-bind: 1.0.2 define-properties: 1.1.4 es-abstract: 1.20.4 - dev: true /string.prototype.trimstart/1.0.5: resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} @@ -19242,7 +19223,6 @@ packages: call-bind: 1.0.2 define-properties: 1.1.4 es-abstract: 1.20.4 - dev: true /string_decoder/1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -20137,7 +20117,6 @@ packages: has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - dev: true /unbzip2-stream/1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} @@ -20687,7 +20666,6 @@ packages: is-generator-function: 1.0.10 is-typed-array: 1.1.9 which-typed-array: 1.1.8 - dev: true /utila/0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -21394,7 +21372,6 @@ packages: is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 - dev: true /which-collection/1.0.1: resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} @@ -21415,7 +21392,6 @@ packages: for-each: 0.3.3 has-tostringtag: 1.0.0 is-typed-array: 1.1.9 - dev: true /which/1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} @@ -21808,7 +21784,6 @@ packages: /yocto-queue/1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - dev: true /zen-observable-ts/1.2.5: resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} From 2be1e087893ec073ea1074ec93d46610da3e040e Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 14:34:49 +0100 Subject: [PATCH 04/47] refactor: cleanup --- .eslintrc | 4 - packages/runner/src/collect.ts | 19 +- packages/runner/src/context.ts | 18 +- .../src/runtime => runner/src}/hooks.ts | 8 +- packages/runner/src/index.ts | 5 +- packages/runner/src/run.ts | 133 ++--- packages/runner/src/suite.ts | 22 +- packages/runner/src/types/runner.ts | 10 +- packages/runner/src/types/tasks.ts | 21 +- packages/runner/src/{ => utils}/chain.ts | 0 packages/runner/src/{ => utils}/error.ts | 0 packages/runner/src/utils/index.ts | 2 + packages/runner/src/utils/tasks.ts | 14 +- packages/utils/src/constants.ts | 1 + packages/utils/src/display.ts | 4 +- packages/utils/src/helpers.ts | 19 + packages/utils/src/index.ts | 1 + packages/utils/src/timers.ts | 46 +- packages/vite-node/src/client.ts | 2 + packages/vitest/LICENSE.md | 4 +- packages/vitest/browser.d.ts | 1 - packages/vitest/package.json | 13 +- packages/vitest/rollup.config.js | 3 - packages/vitest/src/browser.ts | 6 - packages/vitest/src/index.ts | 15 +- .../vitest/src/integrations/chai/index.ts | 2 +- .../src/integrations/snapshot/client.ts | 5 +- packages/vitest/src/node/core.ts | 2 +- packages/vitest/src/node/reporters/base.ts | 4 +- .../reporters/benchmark/table/tableRender.ts | 12 +- packages/vitest/src/node/reporters/dot.ts | 1 - .../node/reporters/renderers/dotRenderer.ts | 2 +- .../node/reporters/renderers/listRenderer.ts | 8 +- packages/vitest/src/runtime/benchmark.ts | 46 ++ packages/vitest/src/runtime/chain.ts | 32 -- packages/vitest/src/runtime/collect.ts | 92 --- packages/vitest/src/runtime/context.ts | 79 --- packages/vitest/src/runtime/entry.ts | 19 +- packages/vitest/src/runtime/error.ts | 175 ------ packages/vitest/src/runtime/map.ts | 34 -- packages/vitest/src/runtime/rpc.ts | 7 +- packages/vitest/src/runtime/run.ts | 528 ------------------ .../vitest/src/runtime/runners/benchmark.ts | 143 +++++ .../vitest/src/runtime/runners/browser.ts | 20 + packages/vitest/src/runtime/runners/node.ts | 129 +++++ packages/vitest/src/runtime/setup.ts | 19 +- packages/vitest/src/runtime/suite.ts | 295 ---------- packages/vitest/src/runtime/test-state.ts | 11 - packages/vitest/src/runtime/utils.ts | 15 - packages/vitest/src/runtime/worker.ts | 2 +- packages/vitest/src/typecheck/collect.ts | 2 +- packages/vitest/src/types/benchmark.ts | 18 +- packages/vitest/src/types/chai.ts | 9 +- packages/vitest/src/types/general.ts | 23 +- packages/vitest/src/types/global.ts | 26 +- packages/vitest/src/types/tasks.ts | 258 +-------- packages/vitest/src/types/worker.ts | 3 +- packages/vitest/src/utils/collect.ts | 94 ---- packages/vitest/src/utils/import.ts | 3 +- packages/vitest/src/utils/index.ts | 26 +- packages/vitest/src/utils/tasks.ts | 43 +- packages/vitest/src/utils/timers.ts | 14 +- pnpm-lock.yaml | 18 +- test/core/package.json | 3 + test/core/test/chainable.test.ts | 2 +- test/core/test/replace-matcher.test.ts | 2 +- test/core/test/serialize.test.ts | 2 +- test/core/vitest.config.ts | 2 +- tsconfig.json | 2 + 69 files changed, 643 insertions(+), 1960 deletions(-) rename packages/{vitest/src/runtime => runner/src}/hooks.ts (84%) rename packages/runner/src/{ => utils}/chain.ts (100%) rename packages/runner/src/{ => utils}/error.ts (100%) create mode 100644 packages/utils/src/constants.ts delete mode 100644 packages/vitest/browser.d.ts delete mode 100644 packages/vitest/src/browser.ts create mode 100644 packages/vitest/src/runtime/benchmark.ts delete mode 100644 packages/vitest/src/runtime/chain.ts delete mode 100644 packages/vitest/src/runtime/collect.ts delete mode 100644 packages/vitest/src/runtime/context.ts delete mode 100644 packages/vitest/src/runtime/error.ts delete mode 100644 packages/vitest/src/runtime/map.ts delete mode 100644 packages/vitest/src/runtime/run.ts create mode 100644 packages/vitest/src/runtime/runners/benchmark.ts create mode 100644 packages/vitest/src/runtime/runners/browser.ts create mode 100644 packages/vitest/src/runtime/runners/node.ts delete mode 100644 packages/vitest/src/runtime/suite.ts delete mode 100644 packages/vitest/src/runtime/test-state.ts delete mode 100644 packages/vitest/src/runtime/utils.ts delete mode 100644 packages/vitest/src/utils/collect.ts diff --git a/.eslintrc b/.eslintrc index ec395ff34572..71bb66544a4e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -27,10 +27,6 @@ "rules": { "no-restricted-globals": [ "error", - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", "performance" ] } diff --git a/packages/runner/src/collect.ts b/packages/runner/src/collect.ts index 6b0af59f30ca..a3d9a66c7bb5 100644 --- a/packages/runner/src/collect.ts +++ b/packages/runner/src/collect.ts @@ -1,10 +1,9 @@ -import { slash } from '@vitest/utils' import type { File } from './types' import type { VitestRunner } from './types/runner' import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from './utils/collect' import { clearCollectorContext, getDefaultSuite } from './suite' import { getHooks, setHooks } from './map' -import { processError } from './error' +import { processError } from './utils/error' import { collectorContext } from './context' import { runSetupFiles } from './setup' @@ -12,24 +11,12 @@ const now = Date.now export async function collectTests(paths: string[], runner: VitestRunner): Promise { const files: File[] = [] - // TODO: move to if(browser) - // const browserHashMap = getWorkerState().browserHashMap! - - // async function importFromBrowser(filepath: string) { - // const match = filepath.match(/^(\w:\/)/) - // const hash = browserHashMap.get(filepath) - // if (match) - // return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) - // else - // return await import(`${filepath}?v=${hash}`) - // } const config = runner.config for (const filepath of paths) { - const url = new URL(filepath, `file://${config.root}`) - const isWindows = /^\w\:\//.test(config.root) - const path = slash(url.href.slice(isWindows ? 8 : 7)) + // TODO /full/path/to/file.js -> /to/file + const path = filepath.slice(config.root.length + 1) const file: File = { id: generateHash(path), name: path, diff --git a/packages/runner/src/context.ts b/packages/runner/src/context.ts index 4278735bc656..c1b20a01735f 100644 --- a/packages/runner/src/context.ts +++ b/packages/runner/src/context.ts @@ -1,7 +1,6 @@ import type { Awaitable } from '@vitest/utils' -import { clearTimeout, setTimeout } from '@vitest/utils' +import { getSafeTimers } from '@vitest/utils' import type { RuntimeContext, SuiteCollector, Test, TestContext } from './types' -// import { getWorkerState } from '../utils' import type { VitestRunner } from './types/runner' export const collectorContext: RuntimeContext = { @@ -28,6 +27,8 @@ export function withTimeout any)>( if (timeout <= 0 || timeout === Infinity) return fn + const { setTimeout, clearTimeout } = getSafeTimers() + return ((...args: (T extends ((...args: infer A) => any) ? A : never)) => { return Promise.race([fn(...args), new Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -47,19 +48,6 @@ export function createTestContext(test: Test, runner: VitestRunner): TestContext context.meta = test - // let _expect: Vi.ExpectStatic | undefined - // Object.defineProperty(context, 'expect', { - // get() { - // if (!_expect) - // _expect = createExpect(test) - // return _expect - // }, - // }) - // Object.defineProperty(context, '_local', { - // get() { - // return _expect != null - // }, - // }) context.onTestFailed = (fn) => { test.onFailed ||= [] test.onFailed.push(fn) diff --git a/packages/vitest/src/runtime/hooks.ts b/packages/runner/src/hooks.ts similarity index 84% rename from packages/vitest/src/runtime/hooks.ts rename to packages/runner/src/hooks.ts index ed391e109aa2..4ff8f4a95785 100644 --- a/packages/vitest/src/runtime/hooks.ts +++ b/packages/runner/src/hooks.ts @@ -1,7 +1,9 @@ -import type { OnTestFailedHandler, SuiteHooks, Test } from '../types' -import { getDefaultHookTimeout, withTimeout } from './context' -import { getCurrentSuite } from './suite' +import type { OnTestFailedHandler, SuiteHooks, Test } from './types' +import { getCurrentSuite, getRunner } from './suite' import { getCurrentTest } from './test-state' +import { withTimeout } from './context' + +const getDefaultHookTimeout = () => getRunner().config.hookTimeout // suite hooks export const beforeAll = (fn: SuiteHooks['beforeAll'][0], timeout?: number) => getCurrentSuite().on('beforeAll', withTimeout(fn, timeout ?? getDefaultHookTimeout(), true)) diff --git a/packages/runner/src/index.ts b/packages/runner/src/index.ts index 2f98ab5f645e..fa84ec6e0a47 100644 --- a/packages/runner/src/index.ts +++ b/packages/runner/src/index.ts @@ -1,3 +1,4 @@ -export { startTests } from './run' -export { processError } from './error' +export { startTests, updateTask } from './run' +export { test, it, describe, suite, getCurrentSuite } from './suite' +export { beforeAll, beforeEach, afterAll, afterEach, onTestFailed } from './hooks' export * from './types' diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index e20b5263ad3b..c61c4d742ac3 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -1,11 +1,11 @@ import limit from 'p-limit' -import { shuffle } from '@vitest/utils' +import { getSafeTimers, shuffle } from '@vitest/utils' import type { VitestRunner } from './types/runner' import type { File, HookCleanupCallback, HookListener, SequenceHooks, Suite, SuiteHooks, Task, TaskResult, TaskState, Test } from './types' import { partitionSuiteChildren } from './utils/suite' import { getFn, getHooks } from './map' import { collectTests } from './collect' -import { processError } from './error' +import { processError } from './utils/error' import { setCurrentTest } from './test-state' import { hasFailed, hasTests } from './utils/tasks' @@ -73,9 +73,11 @@ const packs = new Map() let updateTimer: any let previousUpdate: Promise | undefined -function updateTask(task: Task, runner: VitestRunner) { +export function updateTask(task: Task, runner: VitestRunner) { packs.set(task.id, task.result) + const { clearTimeout, setTimeout } = getSafeTimers() + clearTimeout(updateTimer) updateTimer = setTimeout(() => { previousUpdate = sendTasksUpdate(runner) @@ -83,6 +85,7 @@ function updateTask(task: Task, runner: VitestRunner) { } async function sendTasksUpdate(runner: VitestRunner) { + const { clearTimeout } = getSafeTimers() clearTimeout(updateTimer) await previousUpdate @@ -104,12 +107,8 @@ const callCleanupHooks = async (cleanups: HookCleanupCallback[]) => { export async function runTest(test: Test, runner: VitestRunner) { await runner.onBeforeRunTest?.(test) - if (test.mode !== 'run') { - // TODO: move to hook - // const { getSnapshotClient } = await import('../integrations/snapshot/chai') - // getSnapshotClient().skipTestSnapshots(test) + if (test.mode !== 'run') return - } if (test.result?.state === 'fail') { updateTask(test, runner) @@ -124,58 +123,24 @@ export async function runTest(test: Test, runner: VitestRunner) { } updateTask(test, runner) - // TODO: MOVE TO HOOK - // clearModuleMocks() - setCurrentTest(test) - // if (isNode) { - // const { getSnapshotClient } = await import('../integrations/snapshot/chai') - // await getSnapshotClient().setTest(test) - // } - - // const workerState = getWorkerState() - - // workerState.current = test - const retry = test.retry || 1 for (let retryCount = 0; retryCount < retry; retryCount++) { let beforeEachCleanups: HookCleanupCallback[] = [] try { - // const state: Partial = { - // assertionCalls: 0, - // isExpectingAssertions: false, - // isExpectingAssertionsError: null, - // expectedAssertionsNumber: null, - // expectedAssertionsNumberErrorGen: null, - // testPath: test.suite.file?.filepath, - // currentTestName: getFullName(test), - // // snapshotState: getSnapshotClient().snapshotState, - // } - // setState( - // runner.augmentExpectState?.(state) || state, - // (globalThis as any)[GLOBAL_EXPECT], - // ) await runner.onBeforeTryTest?.(test, retryCount) beforeEachCleanups = await callSuiteHook(test.suite, test, 'beforeEach', runner, [test.context, test.suite]) test.result.retryCount = retryCount - await getFn(test)() + if (runner.runTest) + await runner.runTest(test) + else + await getFn(test)() await runner.onAfterTryTest?.(test, retryCount) - // const { - // assertionCalls, - // expectedAssertionsNumber, - // expectedAssertionsNumberErrorGen, - // isExpectingAssertions, - // isExpectingAssertionsError, - // } = runner.receiveExpectState?.(test) || getState((globalThis as any)[GLOBAL_EXPECT]) - // if (expectedAssertionsNumber !== null && assertionCalls !== expectedAssertionsNumber) - // throw expectedAssertionsNumberErrorGen!() - // if (isExpectingAssertions === true && assertionCalls === 0) - // throw isExpectingAssertionsError test.result.state = 'pass' } @@ -228,16 +193,6 @@ export async function runTest(test: Test, runner: VitestRunner) { await runner.onAfterRunTest?.(test) - // if (isNode) { - // const { getSnapshotClient } = await import('../integrations/snapshot/chai') - // getSnapshotClient().clearTest() - // } - - // if (workerState.config.logHeapUsage && isNode) - // test.result.heap = process.memoryUsage().heapUsed - - // workerState.current = undefined - updateTask(test, runner) } @@ -269,8 +224,6 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { updateTask(suite, runner) - // const workerState = getWorkerState() - if (suite.mode === 'skip') { suite.result.state = 'skip' } @@ -281,22 +234,27 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { try { const beforeAllCleanups = await callSuiteHook(suite, suite, 'beforeAll', runner, [suite]) - for (let tasksGroup of partitionSuiteChildren(suite)) { - if (tasksGroup[0].concurrent === true) { - const mutex = limit(runner.config.maxConcurrency) - await Promise.all(tasksGroup.map(c => mutex(() => runSuiteChild(c, runner)))) - } - else { - const { sequence } = runner.config - if (sequence.shuffle || suite.shuffle) { - // run describe block independently from tests - const suites = tasksGroup.filter(group => group.type === 'suite') - const tests = tasksGroup.filter(group => group.type === 'test') - const groups = shuffle([suites, tests], sequence.seed) - tasksGroup = groups.flatMap(group => shuffle(group, sequence.seed)) + if (runner.runSuite) { + await runner.runSuite(suite) + } + else { + for (let tasksGroup of partitionSuiteChildren(suite)) { + if (tasksGroup[0].concurrent === true) { + const mutex = limit(runner.config.maxConcurrency) + await Promise.all(tasksGroup.map(c => mutex(() => runSuiteChild(c, runner)))) + } + else { + const { sequence } = runner.config + if (sequence.shuffle || suite.shuffle) { + // run describe block independently from tests + const suites = tasksGroup.filter(group => group.type === 'suite') + const tests = tasksGroup.filter(group => group.type === 'test') + const groups = shuffle([suites, tests], sequence.seed) + tasksGroup = groups.flatMap(group => shuffle(group, sequence.seed)) + } + for (const c of tasksGroup) + await runSuiteChild(c, runner) } - for (const c of tasksGroup) - await runSuiteChild(c, runner) } } @@ -314,9 +272,6 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { await runner.onAfterRunSuite?.(suite) - // if (workerState.config.logHeapUsage && isNode) - // suite.result.heap = process.memoryUsage().heapUsed - if (suite.mode === 'run') { if (!hasTests(suite)) { suite.result.state = 'fail' @@ -367,37 +322,13 @@ export async function startTests(paths: string[], runner: VitestRunner) { const files = await collectTests(paths, runner) runner.onCollected?.(files) - // rpc().onCollected(files) - - // const { getSnapshotClient } = await import('../integrations/snapshot/chai') - // getSnapshotClient().clear() + await runner.onBeforeRun?.() await runFiles(files, runner) await runner.onAfterRun?.() - // const coverage = await takeCoverageInsideWorker(config.coverage) - // rpc().onAfterSuiteRun({ coverage }) - - // await getSnapshotClient().saveCurrent() await sendTasksUpdate(runner) return files } - -// export function clearModuleMocks() { -// const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals } = getWorkerState().config - -// // since each function calls another, we can just call one -// if (restoreMocks) -// vi.restoreAllMocks() -// else if (mockReset) -// vi.resetAllMocks() -// else if (clearMocks) -// vi.clearAllMocks() - -// if (unstubEnvs) -// vi.unstubAllEnvs() -// if (unstubGlobals) -// vi.unstubAllGlobals() -// } diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index 0831a227c6ed..2058d2cefc57 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -1,7 +1,7 @@ import { format, isObject, noop, objDisplay, objectAttr } from '@vitest/utils' -import type { File, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Task, Test, TestAPI, TestFunction, TestOptions } from './types' +import type { File, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Task, TaskCustom, Test, TestAPI, TestFunction, TestOptions } from './types' import type { VitestRunner } from './types/runner' -import { createChainable } from './chain' +import { createChainable } from './utils/chain' import { collectTask, collectorContext, createTestContext, runWithSuite, withTimeout } from './context' import { getHooks, setFn, setHooks } from './map' @@ -24,6 +24,10 @@ export function getDefaultSuite() { return defaultSuite } +export function getRunner() { + return runner +} + export function clearCollectorContext(currentRunner: VitestRunner) { if (!defaultSuite) defaultSuite = currentRunner.config.sequence.shuffle ? suite.shuffle('') : suite('') @@ -48,7 +52,7 @@ export function createSuiteHooks() { // implementations function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, shuffle?: boolean, suiteOptions?: number | TestOptions) { - const tasks: (Test | Suite | SuiteCollector)[] = [] + const tasks: (Test | TaskCustom | Suite | SuiteCollector)[] = [] const factoryQueue: (Test | Suite | SuiteCollector)[] = [] let suite: Suite @@ -91,6 +95,17 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m tasks.push(test) }) + const custom = function (name: string) { + const task: TaskCustom = { + id: '', + name, + type: 'custom', + mode: 'run', + } + tasks.push(task) + return task + } + const collector: SuiteCollector = { type: 'collector', name, @@ -98,6 +113,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m test, tasks, collect, + custom, clear, on: addHook, } diff --git a/packages/runner/src/types/runner.ts b/packages/runner/src/types/runner.ts index 231c86398204..ccb141f26736 100644 --- a/packages/runner/src/types/runner.ts +++ b/packages/runner/src/types/runner.ts @@ -5,11 +5,11 @@ export interface VitestRunnerConfig { setupFiles: string[] | string name: string passWithNoTests: boolean - testNamePattern?: string + testNamePattern?: RegExp allowOnly?: boolean sequence: { - shuffle: boolean - seed: number + shuffle?: boolean + seed?: number hooks: SequenceHooks } maxConcurrency: number @@ -29,8 +29,12 @@ export interface VitestRunner { onBeforeRunSuite?(suite: Suite): unknown onAfterRunSuite?(suite: Suite): unknown + runSuite?(suite: Suite): Promise + runTest?(test: Test): Promise + onTaskUpdate?(task: [string, TaskResult | undefined][]): Promise + onBeforeRun?(): unknown onAfterRun?(): unknown importFile(filepath: string): unknown augmentTestContext?(context: TestContext): TestContext diff --git a/packages/runner/src/types/tasks.ts b/packages/runner/src/types/tasks.ts index d8ca45af3caf..c917deb5739e 100644 --- a/packages/runner/src/types/tasks.ts +++ b/packages/runner/src/types/tasks.ts @@ -1,6 +1,6 @@ import type { Awaitable } from '@vitest/utils' -import type { ChainableFunction } from '../chain' -import type { ErrorWithDiff } from '../error' +import type { ChainableFunction } from '../utils/chain' +import type { ErrorWithDiff } from '../utils/error' export type RunMode = 'run' | 'skip' | 'only' | 'todo' export type TaskState = RunMode | 'pass' | 'fail' @@ -15,11 +15,13 @@ export interface TaskBase { file?: File result?: TaskResult retry?: number - // TODO: Augment in "vitest" - // logs?: UserConsoleLog[] meta?: any } +export interface TaskCustom extends TaskBase { + type: 'custom' +} + export interface TaskResult { state: TaskState duration?: number @@ -59,7 +61,7 @@ export interface Test extends TaskBase { onFailed?: OnTestFailedHandler[] } -export type Task = Test | Suite | File +export type Task = Test | Suite | TaskCustom | File export type DoneCallback = (error?: any) => void export type TestFunction = (context: TestContext & ExtraContext) => Awaitable | void @@ -199,7 +201,8 @@ export interface SuiteCollector { readonly mode: RunMode type: 'collector' test: TestAPI - tasks: (Suite | Test | SuiteCollector)[] + tasks: (Suite | TaskCustom | Test | SuiteCollector)[] + custom: (name: string) => TaskCustom collect: (file?: File) => Promise clear: () => void on: >(name: T, ...fn: SuiteHooks[T]) => void @@ -218,12 +221,6 @@ export interface TestContext { */ meta: Readonly - // TODO: augment by "vitest" - /** - * A expect instance bound to the test - */ - // expect: Vi.ExpectStatic - /** * Extract hooks on test failed */ diff --git a/packages/runner/src/chain.ts b/packages/runner/src/utils/chain.ts similarity index 100% rename from packages/runner/src/chain.ts rename to packages/runner/src/utils/chain.ts diff --git a/packages/runner/src/error.ts b/packages/runner/src/utils/error.ts similarity index 100% rename from packages/runner/src/error.ts rename to packages/runner/src/utils/error.ts diff --git a/packages/runner/src/utils/index.ts b/packages/runner/src/utils/index.ts index 1d2a471c247e..ebbe97f11de4 100644 --- a/packages/runner/src/utils/index.ts +++ b/packages/runner/src/utils/index.ts @@ -1,3 +1,5 @@ export * from './collect' export * from './suite' export * from './tasks' +export * from './chain' +export * from './error' diff --git a/packages/runner/src/utils/tasks.ts b/packages/runner/src/utils/tasks.ts index fde9229dbe1a..e80fd52343d3 100644 --- a/packages/runner/src/utils/tasks.ts +++ b/packages/runner/src/utils/tasks.ts @@ -1,11 +1,11 @@ import { type Arrayable, toArray } from '@vitest/utils' -import type { Suite, Task, Test } from '../types' +import type { Suite, Task, TaskCustom, Test } from '../types' -function isAtomTest(s: Task): s is Test { - return (s.type === 'test') +function isAtomTest(s: Task): s is Test | TaskCustom { + return s.type === 'test' || s.type === 'custom' } -export function getTests(suite: Arrayable): (Test)[] { +export function getTests(suite: Arrayable): (Test | TaskCustom)[] { return toArray(suite).flatMap(s => isAtomTest(s) ? [s] : s.tasks.flatMap(c => isAtomTest(c) ? [c] : getTests(c))) } @@ -25,12 +25,6 @@ export function hasFailed(suite: Arrayable): boolean { return toArray(suite).some(s => s.result?.state === 'fail' || (s.type === 'suite' && hasFailed(s.tasks))) } -export function hasFailedSnapshot(suite: Arrayable): boolean { - return getTests(suite).some((s) => { - return s.result?.errors?.some(e => e.message.match(/Snapshot .* mismatched/)) - }) -} - export function getNames(task: Task) { const names = [task.name] let current: Task | undefined = task diff --git a/packages/utils/src/constants.ts b/packages/utils/src/constants.ts new file mode 100644 index 000000000000..04a602aa1405 --- /dev/null +++ b/packages/utils/src/constants.ts @@ -0,0 +1 @@ +export const SAFE_TIMERS_SYMBOL = Symbol('vitest:SAFE_TIMERS') diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts index 5dbd49a0a6cd..59403888944a 100644 --- a/packages/utils/src/display.ts +++ b/packages/utils/src/display.ts @@ -1,6 +1,8 @@ import util from 'util' // @ts-expect-error doesn't have types -import loupe from 'loupe' +import loupeImport from 'loupe' + +const loupe = (typeof loupeImport.default === 'function' ? loupeImport.default : loupeImport) export function format(...args: any[]) { return util.format(...args) diff --git a/packages/utils/src/helpers.ts b/packages/utils/src/helpers.ts index 6aa0bff7c37e..ed03ab42daaa 100644 --- a/packages/utils/src/helpers.ts +++ b/packages/utils/src/helpers.ts @@ -90,3 +90,22 @@ export function objectAttr(source: any, path: string, defaultValue = undefined) } return result } + +type DeferPromise = Promise & { + resolve: (value: T | PromiseLike) => void + reject: (reason?: any) => void +} + +export function createDefer(): DeferPromise { + let resolve: ((value: T | PromiseLike) => void) | null = null + let reject: ((reason?: any) => void) | null = null + + const p = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) as DeferPromise + + p.resolve = resolve! + p.reject = reject! + return p +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 0721002b7842..db6092a84cce 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -5,3 +5,4 @@ export * from './stringify' export * from './timers' export * from './random' export * from './display' +export * from './constants' diff --git a/packages/utils/src/timers.ts b/packages/utils/src/timers.ts index 55b0fd03579c..15ded9f88261 100644 --- a/packages/utils/src/timers.ts +++ b/packages/utils/src/timers.ts @@ -1,13 +1,35 @@ -const { - setTimeout: safeSetTimeout, - setInterval: safeSetInterval, - clearInterval: safeClearInterval, - clearTimeout: safeClearTimeout, -} = globalThis - -export { - safeSetTimeout as setTimeout, - safeSetInterval as setInterval, - safeClearInterval as clearInterval, - safeClearTimeout as clearTimeout, +import { SAFE_TIMERS_SYMBOL } from './constants' + +export function getSafeTimers() { + const { + setTimeout: safeSetTimeout, + setInterval: safeSetInterval, + clearInterval: safeClearInterval, + clearTimeout: safeClearTimeout, + } = (globalThis as any)[SAFE_TIMERS_SYMBOL] || globalThis + + return { + setTimeout: safeSetTimeout, + setInterval: safeSetInterval, + clearInterval: safeClearInterval, + clearTimeout: safeClearTimeout, + } +} + +export function setSafeTimers() { + const { + setTimeout: safeSetTimeout, + setInterval: safeSetInterval, + clearInterval: safeClearInterval, + clearTimeout: safeClearTimeout, + } = globalThis + + const timers = { + setTimeout: safeSetTimeout, + setInterval: safeSetInterval, + clearInterval: safeClearInterval, + clearTimeout: safeClearTimeout, + } + + ;(globalThis as any)[SAFE_TIMERS_SYMBOL] = timers } diff --git a/packages/vite-node/src/client.ts b/packages/vite-node/src/client.ts index e5d5f935bb97..1786957b97c3 100644 --- a/packages/vite-node/src/client.ts +++ b/packages/vite-node/src/client.ts @@ -10,6 +10,8 @@ import { VALID_ID_PREFIX, cleanUrl, isInternalRequest, isPrimitive, normalizeMod import type { HotContext, ModuleCache, ViteNodeRunnerOptions } from './types' import { extractSourceMap } from './source-map' +const { setTimeout, clearTimeout } = globalThis + const debugExecute = createDebug('vite-node:client:execute') const debugNative = createDebug('vite-node:client:native') diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index 60565f1af0d4..bc9405cd664d 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -250,7 +250,7 @@ Repository: chalk/ansi-regex > MIT License > -> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Sindre Sorhus (sindresorhus.com) > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: > @@ -267,7 +267,7 @@ Repository: chalk/ansi-styles > MIT License > -> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Sindre Sorhus (sindresorhus.com) > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: > diff --git a/packages/vitest/browser.d.ts b/packages/vitest/browser.d.ts deleted file mode 100644 index 174e295d4d9d..000000000000 --- a/packages/vitest/browser.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dist/browser' diff --git a/packages/vitest/package.json b/packages/vitest/package.json index b5a693f10f78..902e2b310674 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -49,10 +49,6 @@ "types": "./dist/environments.d.ts", "import": "./dist/environments.js" }, - "./browser": { - "types": "./dist/browser.d.ts", - "import": "./dist/browser.js" - }, "./config": { "types": "./config.d.ts", "require": "./dist/config.cjs", @@ -109,6 +105,11 @@ "@types/chai": "^4.3.4", "@types/chai-subset": "^1.3.3", "@types/node": "*", + "@vitest/expect": "workspace:*", + "@vitest/runner": "workspace:*", + "@vitest/spy": "workspace:*", + "@vitest/ui": "workspace:*", + "@vitest/utils": "workspace:*", "acorn": "^8.8.1", "acorn-walk": "^8.2.0", "cac": "^6.7.14", @@ -136,10 +137,6 @@ "@types/natural-compare": "^1.4.1", "@types/prompts": "^2.4.2", "@types/sinonjs__fake-timers": "^8.1.2", - "@vitest/expect": "workspace:*", - "@vitest/spy": "workspace:*", - "@vitest/ui": "workspace:*", - "@vitest/utils": "workspace:*", "birpc": "^0.2.3", "chai-subset": "^1.6.0", "cli-truncate": "^3.1.0", diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index 79d833cdfaf4..bcbbfb2523c9 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -15,7 +15,6 @@ import pkg from './package.json' const entries = [ 'src/index.ts', - 'src/browser.ts', 'src/node/cli.ts', 'src/node/cli-wrapper.ts', 'src/node.ts', @@ -23,14 +22,12 @@ const entries = [ 'src/runtime/worker.ts', 'src/runtime/loader.ts', 'src/runtime/entry.ts', - 'src/runtime/suite.ts', 'src/integrations/spy.ts', ] const dtsEntries = [ 'src/index.ts', 'src/node.ts', - 'src/browser.ts', 'src/environments.ts', 'src/config.ts', ] diff --git a/packages/vitest/src/browser.ts b/packages/vitest/src/browser.ts deleted file mode 100644 index 8b656e1e98b9..000000000000 --- a/packages/vitest/src/browser.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { suite, test, describe, it } from './runtime/suite' -export * from './runtime/hooks' -export * from './integrations/chai' -export { startTests } from './runtime/run' -export { setupGlobalEnv } from './runtime/setup' -export * from './types' diff --git a/packages/vitest/src/index.ts b/packages/vitest/src/index.ts index 8b8ddedbbc09..340332e2803b 100644 --- a/packages/vitest/src/index.ts +++ b/packages/vitest/src/index.ts @@ -1,6 +1,15 @@ -export { suite, test, describe, it, bench } from './runtime/suite' -export * from './runtime/hooks' -export * from './runtime/utils' +export { + suite, + test, + describe, + it, + beforeAll, + beforeEach, + afterAll, + afterEach, + onTestFailed, +} from '@vitest/runner' +export { bench } from './runtime/benchmark' export { runOnce, isFirstRun } from './integrations/run-once' export * from './integrations/chai' diff --git a/packages/vitest/src/integrations/chai/index.ts b/packages/vitest/src/integrations/chai/index.ts index b245b926ad15..81bd78570ad8 100644 --- a/packages/vitest/src/integrations/chai/index.ts +++ b/packages/vitest/src/integrations/chai/index.ts @@ -1,8 +1,8 @@ import * as chai from 'chai' import './setup' +import type { Test } from '@vitest/runner' import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect' import type { MatcherState } from '../../types/chai' -import type { Test } from '../../types' import { getCurrentEnvironment, getFullName } from '../../utils' export function createExpect(test?: Test) { diff --git a/packages/vitest/src/integrations/snapshot/client.ts b/packages/vitest/src/integrations/snapshot/client.ts index a28dabefcfb9..e1d8c616705c 100644 --- a/packages/vitest/src/integrations/snapshot/client.ts +++ b/packages/vitest/src/integrations/snapshot/client.ts @@ -1,8 +1,9 @@ import { expect } from 'chai' import { equals, iterableEquality, subsetEquality } from '@vitest/expect' -import type { Test } from '../../types' +import type { Test } from '@vitest/runner' +import { getNames } from '@vitest/runner/utils' import { rpc } from '../../runtime/rpc' -import { getNames, getWorkerState } from '../../utils' +import { getWorkerState } from '../../utils' import { deepMergeSnapshot } from './port/utils' import SnapshotState from './port/state' diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 47033d2ef925..45274fd7347a 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -9,7 +9,7 @@ import { ViteNodeRunner } from 'vite-node/client' import { ViteNodeServer } from 'vite-node/server' import type { ArgumentsType, CoverageProvider, OnServerRestartHandler, Reporter, ResolvedConfig, UserConfig, VitestRunMode } from '../types' import { SnapshotManager } from '../integrations/snapshot/manager' -import { clearTimeout, deepMerge, hasFailed, noop, setTimeout, slash, toArray } from '../utils' +import { deepMerge, hasFailed, noop, slash, toArray } from '../utils' import { getCoverageProvider } from '../integrations/coverage' import { Typechecker } from '../typecheck/typechecker' import { createPool } from './pool' diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index 80ac85e139d4..fcfa6fab7190 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -1,7 +1,7 @@ import { performance } from 'perf_hooks' import c from 'picocolors' import type { ErrorWithDiff, File, Reporter, Task, TaskResultPack, UserConsoleLog } from '../../types' -import { clearInterval, getFullName, getSuites, getTests, hasFailed, hasFailedSnapshot, isCI, isNode, relativePath, setInterval } from '../../utils' +import { getFullName, getSafeTimers, getSuites, getTests, hasFailed, hasFailedSnapshot, isCI, isNode, relativePath } from '../../utils' import type { Vitest } from '../../node' import { F_RIGHT } from '../../utils/figures' import { countTestErrors, divider, formatProjectName, formatTimeString, getStateString, getStateSymbol, pointer, renderSnapshotSummary } from './renderers/utils' @@ -123,6 +123,7 @@ export abstract class BaseReporter implements Reporter { ] this.ctx.logger.logUpdate(BADGE_PADDING + LAST_RUN_TEXTS[0]) this._lastRunTimeout = 0 + const { setInterval } = getSafeTimers() this._lastRunTimer = setInterval( () => { this._lastRunTimeout += 1 @@ -137,6 +138,7 @@ export abstract class BaseReporter implements Reporter { } private resetLastRunLog() { + const { clearInterval } = getSafeTimers() clearInterval(this._lastRunTimer) this._lastRunTimer = undefined this.ctx.logger.logUpdate.clear() diff --git a/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts b/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts index aaed86f520c2..ae251fe3d874 100644 --- a/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts +++ b/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts @@ -2,7 +2,7 @@ import c from 'picocolors' import cliTruncate from 'cli-truncate' import stripAnsi from 'strip-ansi' import type { Benchmark, BenchmarkResult, Task } from '../../../../types' -import { clearInterval, getTests, notNullish, setInterval } from '../../../../utils' +import { getTests, notNullish } from '../../../../utils' import { F_RIGHT } from '../../../../utils/figures' import type { Logger } from '../../../logger' import { getCols, getStateSymbol } from '../../renderers/utils' @@ -38,7 +38,7 @@ const tableHead = ['name', 'hz', 'min', 'max', 'mean', 'p75', 'p99', 'p995', 'p9 function renderTableHead(tasks: Task[]) { const benchs = tasks - .map(i => i.type === 'benchmark' ? i.result?.benchmark : undefined) + .map(i => i.meta?.benchmark ? i.result?.benchmark : undefined) .filter(notNullish) const allItems = benchs.map(renderBenchmarkItems).concat([tableHead]) return `${' '.repeat(3)}${tableHead.map((i, idx) => { @@ -70,7 +70,7 @@ function renderBenchmark(task: Benchmark, tasks: Task[]): string { return task.name const benchs = tasks - .map(i => i.type === 'benchmark' ? i.result?.benchmark : undefined) + .map(i => i.meta?.benchmark ? i.result?.benchmark : undefined) .filter(notNullish) const allItems = benchs.map(renderBenchmarkItems).concat([tableHead]) const items = renderBenchmarkItems(result) @@ -108,7 +108,7 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level = for (const task of tasks) { const padding = ' '.repeat(level ? 1 : 0) let prefix = '' - if (idx === 0 && task.type === 'benchmark') + if (idx === 0 && task.meta?.benchmark) prefix += `${renderTableHead(tasks)}\n${padding}` prefix += ` ${getStateSymbol(task)} ` @@ -132,8 +132,8 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level = if (level === 0) name = formatFilepath(name) - const body = task.type === 'benchmark' - ? renderBenchmark(task, tasks) + const body = task.meta?.benchmark + ? renderBenchmark(task as Benchmark, tasks) : name output.push(padding + prefix + body + suffix) diff --git a/packages/vitest/src/node/reporters/dot.ts b/packages/vitest/src/node/reporters/dot.ts index 792e85ce13e7..d6ce952b4130 100644 --- a/packages/vitest/src/node/reporters/dot.ts +++ b/packages/vitest/src/node/reporters/dot.ts @@ -1,5 +1,4 @@ import type { UserConsoleLog } from '../../types' -import { setTimeout } from '../../utils' import { BaseReporter } from './base' import { createDotRenderer } from './renderers/dotRenderer' import type { createListRenderer } from './renderers/listRenderer' diff --git a/packages/vitest/src/node/reporters/renderers/dotRenderer.ts b/packages/vitest/src/node/reporters/renderers/dotRenderer.ts index b478d39c027e..fd0964ce2e1a 100644 --- a/packages/vitest/src/node/reporters/renderers/dotRenderer.ts +++ b/packages/vitest/src/node/reporters/renderers/dotRenderer.ts @@ -1,6 +1,6 @@ import c from 'picocolors' import type { Task } from '../../../types' -import { clearInterval, getTests, setInterval } from '../../../utils' +import { getTests } from '../../../utils' import type { Logger } from '../../logger' export interface DotRendererOptions { diff --git a/packages/vitest/src/node/reporters/renderers/listRenderer.ts b/packages/vitest/src/node/reporters/renderers/listRenderer.ts index d9e353593ec4..8f9b9afcc2d6 100644 --- a/packages/vitest/src/node/reporters/renderers/listRenderer.ts +++ b/packages/vitest/src/node/reporters/renderers/listRenderer.ts @@ -2,7 +2,7 @@ import c from 'picocolors' import cliTruncate from 'cli-truncate' import stripAnsi from 'strip-ansi' import type { Benchmark, BenchmarkResult, SuiteHooks, Task, VitestRunMode } from '../../../types' -import { clearInterval, getTests, notNullish, setInterval } from '../../../utils' +import { getTests, notNullish } from '../../../utils' import { F_RIGHT } from '../../../utils/figures' import type { Logger } from '../../logger' import { formatProjectName, getCols, getHookStateSymbol, getStateSymbol } from './utils' @@ -59,7 +59,7 @@ function renderBenchmark(task: Benchmark, tasks: Task[]): string { return task.name const benchs = tasks - .map(i => i.type === 'benchmark' ? i.result?.benchmark : undefined) + .map(i => i.meta?.benchmark ? i.result?.benchmark : undefined) .filter(notNullish) const allItems = benchs.map(renderBenchmarkItems) @@ -120,8 +120,8 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level = name = formatFilepath(name) const padding = ' '.repeat(level) - const body = task.type === 'benchmark' - ? renderBenchmark(task, tasks) + const body = task.meta?.benchmark + ? renderBenchmark(task as Benchmark, tasks) : name output.push(padding + prefix + body + suffix) diff --git a/packages/vitest/src/runtime/benchmark.ts b/packages/vitest/src/runtime/benchmark.ts new file mode 100644 index 000000000000..d5e6d00e92ee --- /dev/null +++ b/packages/vitest/src/runtime/benchmark.ts @@ -0,0 +1,46 @@ +import type { TaskCustom } from '@vitest/runner' +import { getCurrentSuite } from '@vitest/runner' +import { createChainable } from '@vitest/runner/utils' +import { noop } from '@vitest/utils' +import type { BenchFunction, BenchOptions, BenchmarkAPI } from '../types' + +const benchFns = new WeakMap() +const benchOptsMap = new WeakMap() + +export function getBenchOptions(key: TaskCustom): BenchOptions { + return benchOptsMap.get(key) +} + +export function getBenchFn(key: TaskCustom): BenchFunction { + return benchFns.get(key)! +} + +export const bench = createBenchmark( + (name, fn: BenchFunction = noop, options: BenchOptions = {}) => { + const task = getCurrentSuite().custom(name) + task.meta = { + benchmark: true, + } + benchFns.set(task, fn) + benchOptsMap.set(task, options) + }, +) + +function createBenchmark(fn: ( + ( + this: Record<'skip' | 'only' | 'todo', boolean | undefined>, + name: string, + fn?: BenchFunction, + options?: BenchOptions + ) => void +)) { + const benchmark = createChainable( + ['skip', 'only', 'todo'], + fn, + ) as BenchmarkAPI + + benchmark.skipIf = (condition: any) => (condition ? benchmark.skip : benchmark) as BenchmarkAPI + benchmark.runIf = (condition: any) => (condition ? benchmark : benchmark.skip) as BenchmarkAPI + + return benchmark as BenchmarkAPI +} diff --git a/packages/vitest/src/runtime/chain.ts b/packages/vitest/src/runtime/chain.ts deleted file mode 100644 index 12db6fbb125b..000000000000 --- a/packages/vitest/src/runtime/chain.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type ChainableFunction = { - (...args: Args): R -} & { - [x in T]: ChainableFunction -} & { - fn: (this: Record, ...args: Args) => R -} & E - -export function createChainable( - keys: T[], - fn: (this: Record, ...args: Args) => R, -): ChainableFunction { - function create(context: Record) { - const chain = function (this: any, ...args: Args) { - return fn.apply(context, args) - } - Object.assign(chain, fn) - chain.withContext = () => chain.bind(context) - for (const key of keys) { - Object.defineProperty(chain, key, { - get() { - return create({ ...context, [key]: true }) - }, - }) - } - return chain - } - - const chain = create({} as any) as any - chain.fn = fn - return chain -} diff --git a/packages/vitest/src/runtime/collect.ts b/packages/vitest/src/runtime/collect.ts deleted file mode 100644 index 7d15461f951a..000000000000 --- a/packages/vitest/src/runtime/collect.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { File, ResolvedConfig } from '../types' -import { getWorkerState, isBrowser, relativePath } from '../utils' -import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from '../utils/collect' -import { clearCollectorContext, defaultSuite } from './suite' -import { getHooks, setHooks } from './map' -import { processError } from './error' -import { collectorContext } from './context' -import { runSetupFiles } from './setup' - -const now = Date.now - -export async function collectTests(paths: string[], config: ResolvedConfig): Promise { - const files: File[] = [] - const browserHashMap = getWorkerState().browserHashMap! - - async function importFromBrowser(filepath: string) { - const match = filepath.match(/^(\w:\/)/) - const hash = browserHashMap.get(filepath) - if (match) - return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) - else - return await import(`${filepath}?v=${hash}`) - } - - for (const filepath of paths) { - const path = relativePath(config.root, filepath) - const file: File = { - id: generateHash(path), - name: path, - type: 'suite', - mode: 'run', - filepath, - tasks: [], - projectName: config.name, - } - - clearCollectorContext() - - try { - const setupStart = now() - await runSetupFiles(config) - - const collectStart = now() - file.setupDuration = collectStart - setupStart - if (config.browser && isBrowser) - await importFromBrowser(filepath) - else - await import(filepath) - - const defaultTasks = await defaultSuite.collect(file) - - setHooks(file, getHooks(defaultTasks)) - - for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) { - if (c.type === 'test') { - file.tasks.push(c) - } - else if (c.type === 'benchmark') { - file.tasks.push(c) - } - else if (c.type === 'suite') { - file.tasks.push(c) - } - else if (c.type === 'collector') { - const suite = await c.collect(file) - if (suite.name || suite.tasks.length) - file.tasks.push(suite) - } - } - file.collectDuration = now() - collectStart - } - catch (e) { - const error = processError(e) - file.result = { - state: 'fail', - error, - errors: [error], - } - if (config.browser) - console.error(e) - } - - calculateSuiteHash(file) - - const hasOnlyTasks = someTasksAreOnly(file) - interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly) - - files.push(file) - } - - return files -} diff --git a/packages/vitest/src/runtime/context.ts b/packages/vitest/src/runtime/context.ts deleted file mode 100644 index 2f1fe95f6c8c..000000000000 --- a/packages/vitest/src/runtime/context.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Awaitable, RuntimeContext, SuiteCollector, Test, TestContext } from '../types' -import { createExpect } from '../integrations/chai' -import { clearTimeout, getWorkerState, setTimeout } from '../utils' - -export const collectorContext: RuntimeContext = { - tasks: [], - currentSuite: null, -} - -export function collectTask(task: SuiteCollector) { - collectorContext.currentSuite?.tasks.push(task) -} - -export async function runWithSuite(suite: SuiteCollector, fn: (() => Awaitable)) { - const prev = collectorContext.currentSuite - collectorContext.currentSuite = suite - await fn() - collectorContext.currentSuite = prev -} - -export function getDefaultTestTimeout() { - return getWorkerState().config.testTimeout -} - -export function getDefaultHookTimeout() { - return getWorkerState().config.hookTimeout -} - -export function withTimeout any)>( - fn: T, - timeout = getDefaultTestTimeout(), - isHook = false, -): T { - if (timeout <= 0 || timeout === Infinity) - return fn - - return ((...args: (T extends ((...args: infer A) => any) ? A : never)) => { - return Promise.race([fn(...args), new Promise((resolve, reject) => { - const timer = setTimeout(() => { - clearTimeout(timer) - reject(new Error(makeTimeoutMsg(isHook, timeout))) - }, timeout) - // `unref` might not exist in browser - timer.unref?.() - })]) as Awaitable - }) as T -} - -export function createTestContext(test: Test): TestContext { - const context = function () { - throw new Error('done() callback is deprecated, use promise instead') - } as unknown as TestContext - - context.meta = test - - let _expect: Vi.ExpectStatic | undefined - Object.defineProperty(context, 'expect', { - get() { - if (!_expect) - _expect = createExpect(test) - return _expect - }, - }) - Object.defineProperty(context, '_local', { - get() { - return _expect != null - }, - }) - context.onTestFailed = (fn) => { - test.onFailed ||= [] - test.onFailed.push(fn) - } - - return context -} - -function makeTimeoutMsg(isHook: boolean, timeout: number) { - return `${isHook ? 'Hook' : 'Test'} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? 'hook' : 'test'}, pass a timeout value as the last argument or configure it globally with "${isHook ? 'hookTimeout' : 'testTimeout'}".` -} diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index 27140c837ead..5d90987572b8 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -1,13 +1,15 @@ import { promises as fs } from 'node:fs' import mm from 'micromatch' +import { startTests } from '@vitest/runner' import type { EnvironmentOptions, ResolvedConfig, VitestEnvironment } from '../types' import { getWorkerState, resetModules } from '../utils' import { vi } from '../integrations/vi' import { envs } from '../integrations/env' import { setupGlobalEnv, withEnv } from './setup' -import { startTests } from './run' +import { NodeTestRunner } from './runners/node' +import { NodeBenchmarkRunner } from './runners/benchmark' -function groupBy(collection: T[], iteratee: (item: T) => K) { +function groupBy(collection: T[], iteratee: (item: T) => K) { return collection.reduce((acc, item) => { const key = iteratee(item) acc[key] ||= [] @@ -16,17 +18,16 @@ function groupBy(collection: T[], iterat }, {} as Record) } +// browser shouldn't call this! export async function run(files: string[], config: ResolvedConfig): Promise { await setupGlobalEnv(config) const workerState = getWorkerState() - // TODO @web-runner: we need to figure out how to do this on the browser - if (config.browser) { - workerState.mockMap.clear() - await startTests(files, config) - return - } + // TODO: allow custom runners? + const testRunner = config.mode === 'test' + ? new NodeTestRunner(config) + : new NodeBenchmarkRunner(config) // if calling from a worker, there will always be one file // if calling with no-threads, this will be the whole suite @@ -91,7 +92,7 @@ export async function run(files: string[], config: ResolvedConfig): Promise v && (v[IS_COLLECTION_SYMBOL] || v[IS_RECORD_SYMBOL]) - -const OBJECT_PROTO = Object.getPrototypeOf({}) - -function getUnserializableMessage(err: unknown) { - if (err instanceof Error) - return `: ${err.message}` - if (typeof err === 'string') - return `: ${err}` - return '' -} - -// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm -export function serializeError(val: any, seen = new WeakMap()): any { - if (!val || typeof val === 'string') - return val - if (typeof val === 'function') - return `Function<${val.name || 'anonymous'}>` - if (typeof val === 'symbol') - return val.toString() - if (typeof val !== 'object') - return val - // cannot serialize immutables as immutables - if (isImmutable(val)) - return serializeError(val.toJSON(), seen) - if (val instanceof Promise || (val.constructor && val.constructor.prototype === 'AsyncFunction')) - return 'Promise' - if (typeof Element !== 'undefined' && val instanceof Element) - return val.tagName - if (typeof val.asymmetricMatch === 'function') - return `${val.toString()} ${util.format(val.sample)}` - - if (seen.has(val)) - return seen.get(val) - - if (Array.isArray(val)) { - const clone: any[] = new Array(val.length) - seen.set(val, clone) - val.forEach((e, i) => { - try { - clone[i] = serializeError(e, seen) - } - catch (err) { - clone[i] = getUnserializableMessage(err) - } - }) - return clone - } - else { - // Objects with `Error` constructors appear to cause problems during worker communication - // using `MessagePort`, so the serialized error object is being recreated as plain object. - const clone = Object.create(null) - seen.set(val, clone) - - let obj = val - while (obj && obj !== OBJECT_PROTO) { - Object.getOwnPropertyNames(obj).forEach((key) => { - if (key in clone) - return - try { - clone[key] = serializeError(val[key], seen) - } - catch (err) { - // delete in case it has a setter from prototype that might throw - delete clone[key] - clone[key] = getUnserializableMessage(err) - } - }) - obj = Object.getPrototypeOf(obj) - } - return clone - } -} - -function normalizeErrorMessage(message: string) { - return message.replace(/__vite_ssr_import_\d+__\./g, '') -} - -export function processError(err: any) { - if (!err || typeof err !== 'object') - return err - // stack is not serialized in worker communication - // we stringify it first - if (err.stack) - err.stackStr = String(err.stack) - if (err.name) - err.nameStr = String(err.name) - - const clonedActual = deepClone(err.actual) - const clonedExpected = deepClone(err.expected) - - const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected) - - err.actual = replacedActual - err.expected = replacedExpected - - const workerState = getWorkerState() - const maxDiffSize = workerState.config.outputDiffMaxSize - - if (typeof err.expected !== 'string') - err.expected = stringify(err.expected, 10, { maxLength: maxDiffSize }) - if (typeof err.actual !== 'string') - err.actual = stringify(err.actual, 10, { maxLength: maxDiffSize }) - - // some Error implementations don't allow rewriting message - try { - if (typeof err.message === 'string') - err.message = normalizeErrorMessage(err.message) - - if (typeof err.cause === 'object' && typeof err.cause.message === 'string') - err.cause.message = normalizeErrorMessage(err.cause.message) - } - catch {} - - try { - return serializeError(err) - } - catch (e: any) { - return serializeError(new Error(`Failed to fully serialize error: ${e?.message}\nInner error message: ${err?.message}`)) - } -} - -function isAsymmetricMatcher(data: any) { - const type = getType(data) - return type === 'Object' && typeof data.asymmetricMatch === 'function' -} - -function isReplaceable(obj1: any, obj2: any) { - const obj1Type = getType(obj1) - const obj2Type = getType(obj2) - return obj1Type === obj2Type && obj1Type === 'Object' -} - -export function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced = new WeakSet(), expectedReplaced = new WeakSet()) { - if (!isReplaceable(actual, expected)) - return { replacedActual: actual, replacedExpected: expected } - if (actualReplaced.has(actual) || expectedReplaced.has(expected)) - return { replacedActual: actual, replacedExpected: expected } - actualReplaced.add(actual) - expectedReplaced.add(expected) - ChaiUtil.getOwnEnumerableProperties(expected).forEach((key) => { - const expectedValue = expected[key] - const actualValue = actual[key] - if (isAsymmetricMatcher(expectedValue)) { - if (expectedValue.asymmetricMatch(actualValue)) - actual[key] = expectedValue - } - else if (isAsymmetricMatcher(actualValue)) { - if (actualValue.asymmetricMatch(expectedValue)) - expected[key] = actualValue - } - else if (isReplaceable(actualValue, expectedValue)) { - const replaced = replaceAsymmetricMatcher( - actualValue, - expectedValue, - actualReplaced, - expectedReplaced, - ) - actual[key] = replaced.replacedActual - expected[key] = replaced.replacedExpected - } - }) - return { - replacedActual: actual, - replacedExpected: expected, - } -} diff --git a/packages/vitest/src/runtime/map.ts b/packages/vitest/src/runtime/map.ts deleted file mode 100644 index 4e9cd94efe6b..000000000000 --- a/packages/vitest/src/runtime/map.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Awaitable, BenchFunction, BenchOptions, Benchmark, Suite, SuiteHooks, Test } from '../types' - -// use WeakMap here to make the Test and Suite object serializable -const fnMap = new WeakMap() -const hooksMap = new WeakMap() -const benchOptsMap = new WeakMap() - -export function setFn(key: Test | Benchmark, fn: (() => Awaitable) | BenchFunction) { - fnMap.set(key, fn) -} - -export function getFn(key: Task): Task extends Test ? (() => Awaitable) : BenchFunction { - return fnMap.get(key as any) -} - -export function setHooks(key: Suite, hooks: SuiteHooks) { - hooksMap.set(key, hooks) -} - -export function getHooks(key: Suite): SuiteHooks { - return hooksMap.get(key) -} - -export function isTest(task: Test | Benchmark): task is Test { - return task.type === 'test' -} - -export function setBenchOptions(key: Benchmark, val: BenchOptions) { - benchOptsMap.set(key, val) -} - -export function getBenchOptions(key: Benchmark): BenchOptions { - return benchOptsMap.get(key) -} diff --git a/packages/vitest/src/runtime/rpc.ts b/packages/vitest/src/runtime/rpc.ts index 56c894e362d5..796dfaef40bd 100644 --- a/packages/vitest/src/runtime/rpc.ts +++ b/packages/vitest/src/runtime/rpc.ts @@ -1,11 +1,12 @@ -import { getWorkerState } from '../utils' import { - setTimeout as safeSetTimeout, -} from '../utils/timers' + getSafeTimers, +} from '@vitest/utils' +import { getWorkerState } from '../utils' const safeRandom = Math.random function withSafeTimers(fn: () => void) { + const { setTimeout: safeSetTimeout } = getSafeTimers() const currentSetTimeout = globalThis.setTimeout const currentRandom = globalThis.Math.random diff --git a/packages/vitest/src/runtime/run.ts b/packages/vitest/src/runtime/run.ts deleted file mode 100644 index 45164b75041d..000000000000 --- a/packages/vitest/src/runtime/run.ts +++ /dev/null @@ -1,528 +0,0 @@ -import { performance } from 'perf_hooks' -import limit from 'p-limit' -import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect' -import type { BenchTask, Benchmark, BenchmarkResult, File, HookCleanupCallback, HookListener, ResolvedConfig, SequenceHooks, Suite, SuiteHooks, Task, TaskResult, TaskState, Test } from '../types' -import { vi } from '../integrations/vi' -import { clearTimeout, createDefer, getFullName, getWorkerState, hasFailed, hasTests, isBrowser, isNode, isRunningInBenchmark, partitionSuiteChildren, setTimeout, shuffle } from '../utils' -import { takeCoverageInsideWorker } from '../integrations/coverage' -import type { MatcherState } from '../types/chai' -import { getSnapshotClient } from '../integrations/snapshot/chai' -import { getBenchOptions, getFn, getHooks } from './map' -import { rpc } from './rpc' -import { collectTests } from './collect' -import { processError } from './error' -import { setCurrentTest } from './test-state' - -async function importTinybench() { - if (!globalThis.EventTarget) - await import('event-target-polyfill' as any) - - return (await import('tinybench')) -} - -const now = Date.now - -function updateSuiteHookState(suite: Task, name: keyof SuiteHooks, state: TaskState) { - if (!suite.result) - suite.result = { state: 'run' } - if (!suite.result?.hooks) - suite.result.hooks = {} - const suiteHooks = suite.result.hooks - if (suiteHooks) { - suiteHooks[name] = state - updateTask(suite) - } -} - -function getSuiteHooks(suite: Suite, name: keyof SuiteHooks, sequence: SequenceHooks) { - const hooks = getHooks(suite)[name] - if (sequence === 'stack' && (name === 'afterAll' || name === 'afterEach')) - return hooks.slice().reverse() - return hooks -} - -export async function callSuiteHook( - suite: Suite, - currentTask: Task, - name: T, - args: SuiteHooks[T][0] extends HookListener ? A : never, -): Promise { - const callbacks: HookCleanupCallback[] = [] - if (name === 'beforeEach' && suite.suite) { - callbacks.push( - ...await callSuiteHook(suite.suite, currentTask, name, args), - ) - } - - updateSuiteHookState(currentTask, name, 'run') - - const state = getWorkerState() - const sequence = state.config.sequence.hooks - - const hooks = getSuiteHooks(suite, name, sequence) - - if (sequence === 'parallel') { - callbacks.push(...await Promise.all(hooks.map(fn => fn(...args as any)))) - } - else { - for (const hook of hooks) - callbacks.push(await hook(...args as any)) - } - - updateSuiteHookState(currentTask, name, 'pass') - - if (name === 'afterEach' && suite.suite) { - callbacks.push( - ...await callSuiteHook(suite.suite, currentTask, name, args), - ) - } - - return callbacks -} - -const packs = new Map() -let updateTimer: any -let previousUpdate: Promise | undefined - -function updateTask(task: Task) { - packs.set(task.id, task.result) - - clearTimeout(updateTimer) - updateTimer = setTimeout(() => { - previousUpdate = sendTasksUpdate() - }, 10) -} - -async function sendTasksUpdate() { - clearTimeout(updateTimer) - await previousUpdate - - if (packs.size) { - const p = rpc().onTaskUpdate(Array.from(packs)) - packs.clear() - return p - } -} - -const callCleanupHooks = async (cleanups: HookCleanupCallback[]) => { - await Promise.all(cleanups.map(async (fn) => { - if (typeof fn !== 'function') - return - await fn() - })) -} - -export async function runTest(test: Test) { - if (test.mode !== 'run') { - const { getSnapshotClient } = await import('../integrations/snapshot/chai') - getSnapshotClient().skipTestSnapshots(test) - return - } - - if (test.result?.state === 'fail') { - updateTask(test) - return - } - - const start = now() - - test.result = { - state: 'run', - startTime: start, - } - updateTask(test) - - clearModuleMocks() - - setCurrentTest(test) - - if (isNode) { - const { getSnapshotClient } = await import('../integrations/snapshot/chai') - await getSnapshotClient().setTest(test) - } - - const workerState = getWorkerState() - - workerState.current = test - - const retry = test.retry || 1 - for (let retryCount = 0; retryCount < retry; retryCount++) { - let beforeEachCleanups: HookCleanupCallback[] = [] - try { - setState({ - assertionCalls: 0, - isExpectingAssertions: false, - isExpectingAssertionsError: null, - expectedAssertionsNumber: null, - expectedAssertionsNumberErrorGen: null, - testPath: test.suite.file?.filepath, - currentTestName: getFullName(test), - snapshotState: getSnapshotClient().snapshotState, - }, (globalThis as any)[GLOBAL_EXPECT]) - - beforeEachCleanups = await callSuiteHook(test.suite, test, 'beforeEach', [test.context, test.suite]) - - test.result.retryCount = retryCount - - await getFn(test)() - const { - assertionCalls, - expectedAssertionsNumber, - expectedAssertionsNumberErrorGen, - isExpectingAssertions, - isExpectingAssertionsError, - // @ts-expect-error local is private - } = test.context._local - ? test.context.expect.getState() - : getState((globalThis as any)[GLOBAL_EXPECT]) - if (expectedAssertionsNumber !== null && assertionCalls !== expectedAssertionsNumber) - throw expectedAssertionsNumberErrorGen!() - if (isExpectingAssertions === true && assertionCalls === 0) - throw isExpectingAssertionsError - - test.result.state = 'pass' - } - catch (e) { - const error = processError(e) - test.result.state = 'fail' - test.result.error = error - test.result.errors = [error] - } - - try { - await callSuiteHook(test.suite, test, 'afterEach', [test.context, test.suite]) - await callCleanupHooks(beforeEachCleanups) - } - catch (e) { - const error = processError(e) - test.result.state = 'fail' - test.result.error = error - test.result.errors = [error] - } - - if (test.result.state === 'pass') - break - - // update retry info - updateTask(test) - } - - if (test.result.state === 'fail') - await Promise.all(test.onFailed?.map(fn => fn(test.result!)) || []) - - // if test is marked to be failed, flip the result - if (test.fails) { - if (test.result.state === 'pass') { - const error = processError(new Error('Expect test to fail')) - test.result.state = 'fail' - test.result.error = error - test.result.errors = [error] - } - else { - test.result.state = 'pass' - test.result.error = undefined - test.result.errors = undefined - } - } - - if (isBrowser && test.result.error) - console.error(test.result.error.message, test.result.error.stackStr) - - setCurrentTest(undefined) - - if (isNode) { - const { getSnapshotClient } = await import('../integrations/snapshot/chai') - getSnapshotClient().clearTest() - } - - test.result.duration = now() - start - - if (workerState.config.logHeapUsage && isNode) - test.result.heap = process.memoryUsage().heapUsed - - workerState.current = undefined - - updateTask(test) -} - -function markTasksAsSkipped(suite: Suite) { - suite.tasks.forEach((t) => { - t.mode = 'skip' - t.result = { ...t.result, state: 'skip' } - updateTask(t) - if (t.type === 'suite') - markTasksAsSkipped(t) - }) -} - -export async function runSuite(suite: Suite) { - if (suite.result?.state === 'fail') { - markTasksAsSkipped(suite) - updateTask(suite) - return - } - - const start = now() - - suite.result = { - state: 'run', - startTime: start, - } - - updateTask(suite) - - const workerState = getWorkerState() - - if (suite.mode === 'skip') { - suite.result.state = 'skip' - } - else if (suite.mode === 'todo') { - suite.result.state = 'todo' - } - else { - try { - const beforeAllCleanups = await callSuiteHook(suite, suite, 'beforeAll', [suite]) - - if (isRunningInBenchmark()) { - await runBenchmarkSuite(suite) - } - else { - for (let tasksGroup of partitionSuiteChildren(suite)) { - if (tasksGroup[0].concurrent === true) { - const mutex = limit(workerState.config.maxConcurrency) - await Promise.all(tasksGroup.map(c => mutex(() => runSuiteChild(c)))) - } - else { - const { sequence } = workerState.config - if (sequence.shuffle || suite.shuffle) { - // run describe block independently from tests - const suites = tasksGroup.filter(group => group.type === 'suite') - const tests = tasksGroup.filter(group => group.type === 'test') - const groups = shuffle([suites, tests], sequence.seed) - tasksGroup = groups.flatMap(group => shuffle(group, sequence.seed)) - } - for (const c of tasksGroup) - await runSuiteChild(c) - } - } - } - - await callSuiteHook(suite, suite, 'afterAll', [suite]) - await callCleanupHooks(beforeAllCleanups) - } - catch (e) { - const error = processError(e) - suite.result.state = 'fail' - suite.result.error = error - suite.result.errors = [error] - } - } - suite.result.duration = now() - start - - if (workerState.config.logHeapUsage && isNode) - suite.result.heap = process.memoryUsage().heapUsed - - if (suite.mode === 'run') { - if (!hasTests(suite)) { - suite.result.state = 'fail' - if (!suite.result.error) { - const error = processError(new Error(`No test found in suite ${suite.name}`)) - suite.result.error = error - suite.result.errors = [error] - } - } - else if (hasFailed(suite)) { - suite.result.state = 'fail' - } - else { - suite.result.state = 'pass' - } - } - - updateTask(suite) -} - -function createBenchmarkResult(name: string): BenchmarkResult { - return { - name, - rank: 0, - rme: 0, - samples: [] as number[], - } as BenchmarkResult -} - -async function runBenchmarkSuite(suite: Suite) { - const { Task, Bench } = await importTinybench() - const start = performance.now() - - const benchmarkGroup = [] - const benchmarkSuiteGroup = [] - for (const task of suite.tasks) { - if (task.mode !== 'run') - continue - - if (task.type === 'benchmark') - benchmarkGroup.push(task) - else if (task.type === 'suite') - benchmarkSuiteGroup.push(task) - } - - if (benchmarkSuiteGroup.length) - await Promise.all(benchmarkSuiteGroup.map(subSuite => runBenchmarkSuite(subSuite))) - - if (benchmarkGroup.length) { - const defer = createDefer() - const benchmarkMap: Record = {} - suite.result = { - state: 'run', - startTime: start, - benchmark: createBenchmarkResult(suite.name), - } - updateTask(suite) - benchmarkGroup.forEach((benchmark, idx) => { - const options = getBenchOptions(benchmark) - const benchmarkInstance = new Bench(options) - - const benchmarkFn = getFn(benchmark) - - benchmark.result = { - state: 'run', - startTime: start, - benchmark: createBenchmarkResult(benchmark.name), - } - const id = idx.toString() - benchmarkMap[id] = benchmark - - const task = new Task(benchmarkInstance, id, benchmarkFn) - benchmark.task = task - updateTask(benchmark) - }) - - benchmarkGroup.forEach((benchmark) => { - benchmark.task!.addEventListener('complete', (e) => { - const task = e.task - const _benchmark = benchmarkMap[task.name || ''] - if (_benchmark) { - const taskRes = task.result! - const result = _benchmark.result!.benchmark! - Object.assign(result, taskRes) - updateTask(_benchmark) - } - }) - benchmark.task!.addEventListener('error', (e) => { - const task = e.task - const _benchmark = benchmarkMap[task.name || ''] - defer.reject(_benchmark ? task.result!.error : e) - }) - }) - - Promise.all(benchmarkGroup.map(async (benchmark) => { - await benchmark.task!.warmup() - return await new Promise(resolve => setTimeout(async () => { - resolve(await benchmark.task!.run()) - })) - })).then((tasks) => { - suite.result!.duration = performance.now() - start - suite.result!.state = 'pass' - - tasks - .sort((a, b) => a.result!.mean - b.result!.mean) - .forEach((cycle, idx) => { - const benchmark = benchmarkMap[cycle.name || ''] - benchmark.result!.state = 'pass' - if (benchmark) { - const result = benchmark.result!.benchmark! - result.rank = Number(idx) + 1 - updateTask(benchmark) - } - }) - updateTask(suite) - defer.resolve(null) - }) - - await defer - } -} - -async function runSuiteChild(c: Task) { - if (c.type === 'test') - return runTest(c) - - else if (c.type === 'suite') - return runSuite(c) -} - -async function runSuites(suites: Suite[]) { - for (const suite of suites) - await runSuite(suite) -} - -export async function runFiles(files: File[], config: ResolvedConfig) { - for (const file of files) { - if (!file.tasks.length && !config.passWithNoTests) { - if (!file.result?.errors?.length) { - const error = processError(new Error(`No test suite found in file ${file.filepath}`)) - file.result = { - state: 'fail', - error, - errors: [error], - } - } - } - await runSuite(file) - } -} - -async function startTestsBrowser(paths: string[], config: ResolvedConfig) { - if (isNode) { - rpc().onPathsCollected(paths) - } - else { - const files = await collectTests(paths, config) - await rpc().onCollected(files) - await runSuites(files) - await sendTasksUpdate() - } -} - -async function startTestsNode(paths: string[], config: ResolvedConfig) { - const files = await collectTests(paths, config) - - rpc().onCollected(files) - - const { getSnapshotClient } = await import('../integrations/snapshot/chai') - getSnapshotClient().clear() - - await runFiles(files, config) - - const coverage = await takeCoverageInsideWorker(config.coverage) - rpc().onAfterSuiteRun({ coverage }) - - await getSnapshotClient().saveCurrent() - - await sendTasksUpdate() -} - -export async function startTests(paths: string[], config: ResolvedConfig) { - if (config.browser) - return startTestsBrowser(paths, config) - else - return startTestsNode(paths, config) -} - -export function clearModuleMocks() { - const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals } = getWorkerState().config - - // since each function calls another, we can just call one - if (restoreMocks) - vi.restoreAllMocks() - else if (mockReset) - vi.resetAllMocks() - else if (clearMocks) - vi.clearAllMocks() - - if (unstubEnvs) - vi.unstubAllEnvs() - if (unstubGlobals) - vi.unstubAllGlobals() -} diff --git a/packages/vitest/src/runtime/runners/benchmark.ts b/packages/vitest/src/runtime/runners/benchmark.ts new file mode 100644 index 000000000000..c07e4efe3864 --- /dev/null +++ b/packages/vitest/src/runtime/runners/benchmark.ts @@ -0,0 +1,143 @@ +import { performance } from 'node:perf_hooks' +import type { File, Suite, Task, TaskResult, VitestRunner } from '@vitest/runner' +import { updateTask as updateRunnerTask } from '@vitest/runner' +import { createDefer, getSafeTimers } from '@vitest/utils' +import { rpc } from '../rpc' +import { getBenchFn, getBenchOptions } from '../benchmark' +import type { BenchTask, Benchmark, BenchmarkResult, ResolvedConfig } from '#types' + +async function importTinybench() { + if (!globalThis.EventTarget) + await import('event-target-polyfill' as any) + + return (await import('tinybench')) +} + +function createBenchmarkResult(name: string): BenchmarkResult { + return { + name, + rank: 0, + rme: 0, + samples: [] as number[], + } as BenchmarkResult +} + +async function runBenchmarkSuite(suite: Suite, runner: VitestRunner) { + const { Task, Bench } = await importTinybench() + const start = performance.now() + + const benchmarkGroup: Benchmark[] = [] + const benchmarkSuiteGroup = [] + for (const task of suite.tasks) { + if (task.mode !== 'run') + continue + + if (task.meta?.benchmark) + benchmarkGroup.push(task as Benchmark) + else if (task.type === 'suite') + benchmarkSuiteGroup.push(task) + } + + if (benchmarkSuiteGroup.length) + await Promise.all(benchmarkSuiteGroup.map(subSuite => runBenchmarkSuite(subSuite, runner))) + + if (benchmarkGroup.length) { + const defer = createDefer() + const benchmarkMap: Record = {} + suite.result = { + state: 'run', + startTime: start, + benchmark: createBenchmarkResult(suite.name), + } + updateTask(suite) + benchmarkGroup.forEach((benchmark, idx) => { + const options = getBenchOptions(benchmark) + const benchmarkInstance = new Bench(options) + + const benchmarkFn = getBenchFn(benchmark) + + benchmark.result = { + state: 'run', + startTime: start, + benchmark: createBenchmarkResult(benchmark.name), + } + const id = idx.toString() + benchmarkMap[id] = benchmark + + const task = new Task(benchmarkInstance, id, benchmarkFn) + benchmark.meta.task = task + updateTask(benchmark) + }) + + benchmarkGroup.forEach((benchmark) => { + benchmark.meta.task!.addEventListener('complete', (e) => { + const task = e.task + const _benchmark = benchmarkMap[task.name || ''] + if (_benchmark) { + const taskRes = task.result! + const result = _benchmark.result!.benchmark! + Object.assign(result, taskRes) + updateTask(_benchmark) + } + }) + benchmark.meta.task!.addEventListener('error', (e) => { + const task = e.task + const _benchmark = benchmarkMap[task.name || ''] + defer.reject(_benchmark ? task.result!.error : e) + }) + }) + + Promise.all(benchmarkGroup.map(async (benchmark) => { + await benchmark.meta.task!.warmup() + const { setTimeout } = getSafeTimers() + return await new Promise(resolve => setTimeout(async () => { + resolve(await benchmark.meta.task!.run()) + })) + })).then((tasks) => { + suite.result!.duration = performance.now() - start + suite.result!.state = 'pass' + + tasks + .sort((a, b) => a.result!.mean - b.result!.mean) + .forEach((cycle, idx) => { + const benchmark = benchmarkMap[cycle.name || ''] + benchmark.result!.state = 'pass' + if (benchmark) { + const result = benchmark.result!.benchmark! + result.rank = Number(idx) + 1 + updateTask(benchmark) + } + }) + updateTask(suite) + defer.resolve(null) + }) + + await defer + } + + function updateTask(task: Task) { + updateRunnerTask(task, runner) + } +} + +export class NodeBenchmarkRunner implements VitestRunner { + private rpc = rpc() + + constructor(public config: ResolvedConfig) {} + + importFile(filepath: string): unknown { + return import(filepath) + } + + onCollected(files: File[]) { + this.rpc.onCollected(files) + } + + async runSuite(suite: Suite): Promise { + await runBenchmarkSuite(suite, this) + } + + onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { + return this.rpc.onTaskUpdate(task) + } +} diff --git a/packages/vitest/src/runtime/runners/browser.ts b/packages/vitest/src/runtime/runners/browser.ts new file mode 100644 index 000000000000..006adfbb7a3e --- /dev/null +++ b/packages/vitest/src/runtime/runners/browser.ts @@ -0,0 +1,20 @@ +import type { VitestRunner } from '@vitest/runner' +import type { ResolvedConfig } from '#types' + +// TODO should be inside browser package +export class BrowserTestRunner implements VitestRunner { + hasMap = new Map() + + constructor(public config: ResolvedConfig) { + this.config = config + } + + async importFile(filepath: string) { + const match = filepath.match(/^(\w:\/)/) + const hash = this.hasMap.get(filepath) + if (match) + return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) + else + return await import(`${filepath}?v=${hash}`) + } +} diff --git a/packages/vitest/src/runtime/runners/node.ts b/packages/vitest/src/runtime/runners/node.ts new file mode 100644 index 000000000000..553ec90df1a2 --- /dev/null +++ b/packages/vitest/src/runtime/runners/node.ts @@ -0,0 +1,129 @@ +import process from 'node:process' +import type { File, Suite, TaskResult, Test, TestContext, VitestRunner } from '@vitest/runner' +import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect' +import { rpc } from '../rpc' +import { getSnapshotClient } from '../../integrations/snapshot/chai' +import { vi } from '../../integrations/vi' +import { takeCoverageInsideWorker } from '../../integrations/coverage' +import { getFullName, getWorkerState } from '../../utils' +import { createExpect } from '../../integrations/chai/index' +import type { ResolvedConfig } from '#types' + +export class NodeTestRunner implements VitestRunner { + private snapshotClient = getSnapshotClient() + private workerState = getWorkerState() + + constructor(public config: ResolvedConfig) {} + + importFile(filepath: string): unknown { + return import(filepath) + } + + onCollected(files: File[]) { + rpc().onCollected(files) + } + + onBeforeRun() { + this.snapshotClient.clear() + } + + async onAfterRun() { + const coverage = await takeCoverageInsideWorker(this.config.coverage) + rpc().onAfterSuiteRun({ coverage }) + this.snapshotClient.saveCurrent() + } + + onAfterRunSuite(suite: Suite) { + if (this.config.logHeapUsage) + suite.result!.heap = process.memoryUsage().heapUsed + } + + onAfterRunTest(test: Test) { + this.snapshotClient.clearTest() + + if (this.config.logHeapUsage) + test.result!.heap = process.memoryUsage().heapUsed + + this.workerState.current = undefined + } + + async onBeforeRunTest(test: Test) { + if (test.mode !== 'run') { + this.snapshotClient.skipTestSnapshots(test) + return + } + + clearModuleMocks(this.config) + await this.snapshotClient.setTest(test) + + this.workerState.current = test + } + + onBeforeTryTest(test: Test) { + setState({ + assertionCalls: 0, + isExpectingAssertions: false, + isExpectingAssertionsError: null, + expectedAssertionsNumber: null, + expectedAssertionsNumberErrorGen: null, + testPath: test.suite.file?.filepath, + currentTestName: getFullName(test), + snapshotState: this.snapshotClient.snapshotState, + }, (globalThis as any)[GLOBAL_EXPECT]) + } + + onAfterTryTest(test: Test) { + const { + assertionCalls, + expectedAssertionsNumber, + expectedAssertionsNumberErrorGen, + isExpectingAssertions, + isExpectingAssertionsError, + // @ts-expect-error local is untyped + } = test.context._local + ? test.context.expect.getState() + : getState((globalThis as any)[GLOBAL_EXPECT]) + if (expectedAssertionsNumber !== null && assertionCalls !== expectedAssertionsNumber) + throw expectedAssertionsNumberErrorGen!() + if (isExpectingAssertions === true && assertionCalls === 0) + throw isExpectingAssertionsError + } + + augmentTestContext(context: TestContext): TestContext { + let _expect: Vi.ExpectStatic | undefined + Object.defineProperty(context, 'expect', { + get() { + if (!_expect) + _expect = createExpect(context.meta) + return _expect + }, + }) + Object.defineProperty(context, '_local', { + get() { + return _expect != null + }, + }) + return context + } + + onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { + return rpc().onTaskUpdate(task) + } +} + +function clearModuleMocks(config: ResolvedConfig) { + const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals } = config + + // since each function calls another, we can just call one + if (restoreMocks) + vi.restoreAllMocks() + else if (mockReset) + vi.resetAllMocks() + else if (clearMocks) + vi.clearAllMocks() + + if (unstubEnvs) + vi.unstubAllEnvs() + if (unstubGlobals) + vi.unstubAllGlobals() +} diff --git a/packages/vitest/src/runtime/setup.ts b/packages/vitest/src/runtime/setup.ts index 40dddf244177..eb1b026c76cc 100644 --- a/packages/vitest/src/runtime/setup.ts +++ b/packages/vitest/src/runtime/setup.ts @@ -1,9 +1,10 @@ /* eslint-disable n/no-deprecated-api */ import { installSourcemapsSupport } from 'vite-node/source-map' +import { setSafeTimers } from '@vitest/utils' import { environments } from '../integrations/env' import type { Environment, ResolvedConfig } from '../types' -import { clearTimeout, getWorkerState, isNode, setTimeout, toArray } from '../utils' +import { getSafeTimers, getWorkerState, isNode } from '../utils' import * as VitestIndex from '../index' import { resetRunOnceCounter } from '../integrations/run-once' import { RealDate } from '../integrations/mock/date' @@ -26,13 +27,14 @@ export async function setupGlobalEnv(config: ResolvedConfig) { if (globalSetup) return + setSafeTimers() + globalSetup = true + // always mock "required" `css` files, because we cannot process them require.extensions['.css'] = () => ({}) require.extensions['.scss'] = () => ({}) require.extensions['.sass'] = () => ({}) - globalSetup = true - if (isNode) { const state = getWorkerState() @@ -60,6 +62,7 @@ export async function setupConsoleLogSpy() { const { Writable } = await import('node:stream') const { Console } = await import('node:console') + const { setTimeout, clearTimeout } = getSafeTimers() // group sync console.log calls with macro task function schedule(taskId: string) { @@ -194,13 +197,3 @@ export async function withEnv( await env.teardown(globalThis) } } - -export async function runSetupFiles(config: ResolvedConfig) { - const files = toArray(config.setupFiles) - await Promise.all( - files.map(async (fsPath) => { - getWorkerState().moduleCache.delete(fsPath) - await import(fsPath) - }), - ) -} diff --git a/packages/vitest/src/runtime/suite.ts b/packages/vitest/src/runtime/suite.ts deleted file mode 100644 index 8a69f6bc05e1..000000000000 --- a/packages/vitest/src/runtime/suite.ts +++ /dev/null @@ -1,295 +0,0 @@ -import util from 'util' -import { util as chaiUtil } from 'chai' -import type { BenchFunction, BenchOptions, Benchmark, BenchmarkAPI, File, RunMode, Suite, SuiteAPI, SuiteCollector, SuiteFactory, SuiteHooks, Task, Test, TestAPI, TestFunction, TestOptions } from '../types' -import { getWorkerState, isObject, isRunningInBenchmark, isRunningInTest, noop, objectAttr } from '../utils' -import { createChainable } from './chain' -import { collectTask, collectorContext, createTestContext, runWithSuite, withTimeout } from './context' -import { getHooks, setBenchOptions, setFn, setHooks } from './map' - -// apis -export const suite = createSuite() -export const test = createTest( - function (name: string, fn?: TestFunction, options?: number | TestOptions) { - getCurrentSuite().test.fn.call(this, name, fn, options) - }, -) -export const bench = createBenchmark( - function (name, fn: BenchFunction = noop, options: BenchOptions = {}) { - getCurrentSuite().benchmark.fn.call(this, name, fn, options) - }, -) - -// alias -export const describe = suite -export const it = test - -const workerState = getWorkerState() - -export const defaultSuite = workerState.config.sequence.shuffle - ? suite.shuffle('') - : suite('') - -export function clearCollectorContext() { - collectorContext.tasks.length = 0 - defaultSuite.clear() - collectorContext.currentSuite = defaultSuite -} - -export function getCurrentSuite() { - return (collectorContext.currentSuite || defaultSuite) as SuiteCollector -} - -export function createSuiteHooks() { - return { - beforeAll: [], - afterAll: [], - beforeEach: [], - afterEach: [], - } -} - -// implementations -function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, mode: RunMode, concurrent?: boolean, shuffle?: boolean, suiteOptions?: number | TestOptions) { - const tasks: (Benchmark | Test | Suite | SuiteCollector)[] = [] - const factoryQueue: (Test | Suite | SuiteCollector)[] = [] - - let suite: Suite - - initSuite() - - const test = createTest(function (name: string, fn = noop, options = suiteOptions) { - if (!isRunningInTest()) - throw new Error('`test()` and `it()` is only available in test mode.') - - const mode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run' - - if (typeof options === 'number') - options = { timeout: options } - - const test: Test = { - id: '', - type: 'test', - name, - mode, - suite: undefined!, - fails: this.fails, - retry: options?.retry, - } as Omit as Test - - if (this.concurrent || concurrent) - test.concurrent = true - if (shuffle) - test.shuffle = true - - const context = createTestContext(test) - // create test context - Object.defineProperty(test, 'context', { - value: context, - enumerable: false, - }) - - setFn(test, withTimeout( - () => fn(context), - options?.timeout, - )) - - tasks.push(test) - }) - - const benchmark = createBenchmark(function (name: string, fn = noop, options: BenchOptions = {}) { - const mode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run' - - if (!isRunningInBenchmark()) - throw new Error('`bench()` is only available in benchmark mode. Run with `vitest bench` instead.') - - const benchmark: Benchmark = { - type: 'benchmark', - id: '', - name, - mode, - suite: undefined!, - } - - setFn(benchmark, fn) - setBenchOptions(benchmark, options) - tasks.push(benchmark) - }) - - const collector: SuiteCollector = { - type: 'collector', - name, - mode, - test, - tasks, - benchmark, - collect, - clear, - on: addHook, - } - - function addHook(name: T, ...fn: SuiteHooks[T]) { - getHooks(suite)[name].push(...fn as any) - } - - function initSuite() { - suite = { - id: '', - type: 'suite', - name, - mode, - shuffle, - tasks: [], - } - setHooks(suite, createSuiteHooks()) - } - - function clear() { - tasks.length = 0 - factoryQueue.length = 0 - initSuite() - } - - async function collect(file?: File) { - factoryQueue.length = 0 - if (factory) - await runWithSuite(collector, () => factory(test)) - - const allChildren: Task[] = [] - - for (const i of [...factoryQueue, ...tasks]) - allChildren.push(i.type === 'collector' ? await i.collect(file) : i) - - suite.file = file - suite.tasks = allChildren - - allChildren.forEach((task) => { - task.suite = suite - if (file) - task.file = file - }) - - return suite - } - - collectTask(collector) - return collector -} - -function createSuite() { - function suiteFn(this: Record, name: string, factory?: SuiteFactory, options?: number | TestOptions) { - const mode: RunMode = this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run' - return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options) - } - - suiteFn.each = function(this: { withContext: () => SuiteAPI }, cases: ReadonlyArray, ...args: any[]) { - const suite = this.withContext() - - if (Array.isArray(cases) && args.length) - cases = formatTemplateString(cases, args) - - return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => { - const arrayOnlyCases = cases.every(Array.isArray) - cases.forEach((i, idx) => { - const items = Array.isArray(i) ? i : [i] - arrayOnlyCases - ? suite(formatTitle(name, items, idx), () => fn(...items), options) - : suite(formatTitle(name, items, idx), () => fn(i), options) - }) - } - } - - suiteFn.skipIf = (condition: any) => (condition ? suite.skip : suite) as SuiteAPI - suiteFn.runIf = (condition: any) => (condition ? suite : suite.skip) as SuiteAPI - - return createChainable( - ['concurrent', 'shuffle', 'skip', 'only', 'todo'], - suiteFn, - ) as unknown as SuiteAPI -} - -function createTest(fn: ( - ( - this: Record<'concurrent' | 'skip' | 'only' | 'todo' | 'fails', boolean | undefined>, - title: string, - fn?: TestFunction, - options?: number | TestOptions - ) => void -)) { - const testFn = fn as any - - testFn.each = function(this: { withContext: () => TestAPI }, cases: ReadonlyArray, ...args: any[]) { - const test = this.withContext() - - if (Array.isArray(cases) && args.length) - cases = formatTemplateString(cases, args) - - return (name: string, fn: (...args: T[]) => void, options?: number | TestOptions) => { - const arrayOnlyCases = cases.every(Array.isArray) - cases.forEach((i, idx) => { - const items = Array.isArray(i) ? i : [i] - - arrayOnlyCases - ? test(formatTitle(name, items, idx), () => fn(...items), options) - : test(formatTitle(name, items, idx), () => fn(i), options) - }) - } - } - - testFn.skipIf = (condition: any) => (condition ? test.skip : test) as TestAPI - testFn.runIf = (condition: any) => (condition ? test : test.skip) as TestAPI - - return createChainable( - ['concurrent', 'skip', 'only', 'todo', 'fails'], - testFn, - ) as TestAPI -} - -function createBenchmark(fn: ( - ( - this: Record<'skip' | 'only' | 'todo', boolean | undefined>, - name: string, - fn?: BenchFunction, - options?: BenchOptions - ) => void -)) { - const benchmark = createChainable( - ['skip', 'only', 'todo'], - fn, - ) as BenchmarkAPI - - benchmark.skipIf = (condition: any) => (condition ? benchmark.skip : benchmark) as BenchmarkAPI - benchmark.runIf = (condition: any) => (condition ? benchmark : benchmark.skip) as BenchmarkAPI - - return benchmark as BenchmarkAPI -} - -function formatTitle(template: string, items: any[], idx: number) { - if (template.includes('%#')) { - // '%#' match index of the test case - template = template - .replace(/%%/g, '__vitest_escaped_%__') - .replace(/%#/g, `${idx}`) - .replace(/__vitest_escaped_%__/g, '%%') - } - const count = template.split('%').length - 1 - let formatted = util.format(template, ...items.slice(0, count)) - if (isObject(items[0])) { - formatted = formatted.replace(/\$([$\w_.]+)/g, - (_, key) => chaiUtil.objDisplay(objectAttr(items[0], key)) as unknown as string, - // https://github.com/chaijs/chai/pull/1490 - ) - } - return formatted -} - -function formatTemplateString(cases: any[], args: any[]): any[] { - const header = cases.join('').trim().replace(/ /g, '').split('\n').map(i => i.split('|'))[0] - const res: any[] = [] - for (let i = 0; i < Math.floor((args.length) / header.length); i++) { - const oneCase: Record = {} - for (let j = 0; j < header.length; j++) - oneCase[header[j]] = args[i * header.length + j] as any - res.push(oneCase) - } - return res -} diff --git a/packages/vitest/src/runtime/test-state.ts b/packages/vitest/src/runtime/test-state.ts deleted file mode 100644 index 26c81cf024f8..000000000000 --- a/packages/vitest/src/runtime/test-state.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Test } from '../types' - -let _test: Test | undefined - -export function setCurrentTest(test: Test | undefined) { - _test = test -} - -export function getCurrentTest() { - return _test -} diff --git a/packages/vitest/src/runtime/utils.ts b/packages/vitest/src/runtime/utils.ts deleted file mode 100644 index 151745563aeb..000000000000 --- a/packages/vitest/src/runtime/utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { DoneCallback } from '../types' - -/** - * A simple wrapper for converting callback style to promise - */ -export function withCallback(fn: (done: DoneCallback) => void): Promise { - return new Promise((resolve, reject) => - fn((err) => { - if (err) - reject(err) - else - resolve() - }), - ) -} diff --git a/packages/vitest/src/runtime/worker.ts b/packages/vitest/src/runtime/worker.ts index e769f5da1f40..c21c2b5545c8 100644 --- a/packages/vitest/src/runtime/worker.ts +++ b/packages/vitest/src/runtime/worker.ts @@ -1,6 +1,7 @@ import { relative, resolve } from 'pathe' import { createBirpc } from 'birpc' import { workerId as poolId } from 'tinypool' +import { processError } from '@vitest/runner/utils' import { ModuleCacheMap } from 'vite-node/client' import { isPrimitive } from 'vite-node/utils' import type { ResolvedConfig, WorkerContext, WorkerRPC } from '../types' @@ -9,7 +10,6 @@ import { getWorkerState } from '../utils' import type { MockMap } from '../types/mocker' import { executeInViteNode } from './execute' import { rpc } from './rpc' -import { processError } from './error' let _viteNode: { run: (files: string[], config: ResolvedConfig) => Promise diff --git a/packages/vitest/src/typecheck/collect.ts b/packages/vitest/src/typecheck/collect.ts index 0916b499be13..a30f80cc47f7 100644 --- a/packages/vitest/src/typecheck/collect.ts +++ b/packages/vitest/src/typecheck/collect.ts @@ -3,8 +3,8 @@ import { parse as parseAst } from 'acorn' import { ancestor as walkAst } from 'acorn-walk' import type { RawSourceMap } from 'vite-node' +import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from '@vitest/runner/utils' import type { File, Suite, Test, Vitest } from '../types' -import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from '../utils/collect' interface ParsedFile extends File { start: number diff --git a/packages/vitest/src/types/benchmark.ts b/packages/vitest/src/types/benchmark.ts index c114e258d2bf..03a9bfc40b68 100644 --- a/packages/vitest/src/types/benchmark.ts +++ b/packages/vitest/src/types/benchmark.ts @@ -1,7 +1,9 @@ +import type { TaskCustom } from '@vitest/runner' +import type { ChainableFunction } from '@vitest/runner/utils' +import type { Arrayable } from '@vitest/utils' import type { Bench as BenchFactory, Options as BenchOptions, Task as BenchTask, TaskResult as BenchTaskResult, TaskResult as TinybenchResult } from 'tinybench' import type { BenchmarkBuiltinReporters } from '../node/reporters' -import type { ChainableFunction } from '../runtime/chain' -import type { Arrayable, Reporter, Suite, TaskBase, TaskResult } from '.' +import type { Reporter } from './reporter' export interface BenchmarkUserOptions { /** @@ -37,12 +39,12 @@ export interface BenchmarkUserOptions { outputFile?: string | (Partial> & Record) } -export interface Benchmark extends TaskBase { - type: 'benchmark' - suite: Suite - result?: TaskResult - fails?: boolean - task?: BenchTask +export interface Benchmark extends TaskCustom { + meta: { + benchmark: true + task?: BenchTask + result?: BenchTaskResult + } } export interface BenchmarkResult extends TinybenchResult { diff --git a/packages/vitest/src/types/chai.ts b/packages/vitest/src/types/chai.ts index 94069cfe9162..a922590e82b9 100644 --- a/packages/vitest/src/types/chai.ts +++ b/packages/vitest/src/types/chai.ts @@ -1,8 +1 @@ -import type { MatcherState as JestMatcherState } from '@vitest/expect' -import type SnapshotState from '../integrations/snapshot/port/state' -import type { VitestEnvironment } from './config' - -export interface MatcherState extends JestMatcherState { - environment: VitestEnvironment - snapshotState: SnapshotState -} +export { MatcherState } from '@vitest/expect' diff --git a/packages/vitest/src/types/general.ts b/packages/vitest/src/types/general.ts index f51be6877839..d2eb8c8cc851 100644 --- a/packages/vitest/src/types/general.ts +++ b/packages/vitest/src/types/general.ts @@ -1,3 +1,5 @@ +export type { ErrorWithDiff, ParsedStack } from '@vitest/runner/utils' + export type Awaitable = T | PromiseLike export type Nullable = T | null | undefined export type Arrayable = T | Array @@ -47,27 +49,6 @@ export interface UserConsoleLog { size: number } -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 - actual?: any - expected?: any - operator?: string - type?: string - frame?: string -} - export interface ModuleGraphData { graph: Record externalized: string[] diff --git a/packages/vitest/src/types/global.ts b/packages/vitest/src/types/global.ts index a1b87d37dae6..bfcd580dfef2 100644 --- a/packages/vitest/src/types/global.ts +++ b/packages/vitest/src/types/global.ts @@ -1,7 +1,10 @@ import type { Plugin as PrettyFormatPlugin } from 'pretty-format' import type { MatchersObject } from '@vitest/expect' +import type SnapshotState from '../integrations/snapshot/port/state' import type { MatcherState } from './chai' -import type { Constructable } from './general' +import type { Constructable, UserConsoleLog } from './general' +import type { VitestEnvironment } from './config' +import type { BenchmarkResult } from './benchmark' type Promisify = { [K in keyof O]: O[K] extends (...args: infer A) => infer R @@ -11,6 +14,27 @@ type Promisify = { : O[K] } +declare module '@vitest/expect' { + interface MatcherState { + environment: VitestEnvironment + snapshotState: SnapshotState + } +} + +declare module '@vitest/runner' { + interface TestContext { + expect: Vi.ExpectStatic + } + + interface TaskBase { + logs?: UserConsoleLog[] + } + + interface TaskResult { + benchmark?: BenchmarkResult + } +} + declare global { // support augmenting jest.Matchers by other libraries namespace jest { diff --git a/packages/vitest/src/types/tasks.ts b/packages/vitest/src/types/tasks.ts index da62441943ce..9ce97e435e90 100644 --- a/packages/vitest/src/types/tasks.ts +++ b/packages/vitest/src/types/tasks.ts @@ -1,234 +1,24 @@ -import type { ChainableFunction } from '../runtime/chain' -import type { BenchFactory, Benchmark, BenchmarkAPI, BenchmarkResult } from './benchmark' -import type { Awaitable, ErrorWithDiff, UserConsoleLog } from './general' - -export type RunMode = 'run' | 'skip' | 'only' | 'todo' -export type TaskState = RunMode | 'pass' | 'fail' - -export interface TaskBase { - id: string - name: string - mode: RunMode - concurrent?: boolean - shuffle?: boolean - suite?: Suite - file?: File - result?: TaskResult - retry?: number - logs?: UserConsoleLog[] - meta?: any -} - -export interface TaskResult { - state: TaskState - duration?: number - startTime?: number - heap?: number - /** - * @deprecated Use "errors" instead - */ - error?: ErrorWithDiff - errors?: ErrorWithDiff[] - htmlError?: string - hooks?: Partial> - benchmark?: BenchmarkResult - retryCount?: number -} - -export type TaskResultPack = [id: string, result: TaskResult | undefined] - -export interface Suite extends TaskBase { - type: 'suite' - tasks: Task[] - filepath?: string - benchmark?: BenchFactory - projectName?: string -} - -export interface File extends Suite { - filepath: string - collectDuration?: number - setupDuration?: number -} - -export interface Test extends TaskBase { - type: 'test' - suite: Suite - result?: TaskResult - fails?: boolean - context: TestContext & ExtraContext - onFailed?: OnTestFailedHandler[] -} - -export type Task = Test | Suite | File | Benchmark - -export type DoneCallback = (error?: any) => void -export type TestFunction = (context: TestContext & ExtraContext) => Awaitable | void - -// jest's ExtractEachCallbackArgs -type ExtractEachCallbackArgs> = { - 1: [T[0]] - 2: [T[0], T[1]] - 3: [T[0], T[1], T[2]] - 4: [T[0], T[1], T[2], T[3]] - 5: [T[0], T[1], T[2], T[3], T[4]] - 6: [T[0], T[1], T[2], T[3], T[4], T[5]] - 7: [T[0], T[1], T[2], T[3], T[4], T[5], T[6]] - 8: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7]] - 9: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], T[8]] - 10: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], T[8], T[9]] - fallback: Array ? U : any> -}[T extends Readonly<[any]> - ? 1 - : T extends Readonly<[any, any]> - ? 2 - : T extends Readonly<[any, any, any]> - ? 3 - : T extends Readonly<[any, any, any, any]> - ? 4 - : T extends Readonly<[any, any, any, any, any]> - ? 5 - : T extends Readonly<[any, any, any, any, any, any]> - ? 6 - : T extends Readonly<[any, any, any, any, any, any, any]> - ? 7 - : T extends Readonly<[any, any, any, any, any, any, any, any]> - ? 8 - : T extends Readonly<[any, any, any, any, any, any, any, any, any]> - ? 9 - : T extends Readonly<[any, any, any, any, any, any, any, any, any, any]> - ? 10 - : 'fallback'] - -interface SuiteEachFunction { - (cases: ReadonlyArray): ( - name: string, - fn: (...args: T) => Awaitable, - ) => void - >(cases: ReadonlyArray): ( - name: string, - fn: (...args: ExtractEachCallbackArgs) => Awaitable, - ) => void - (cases: ReadonlyArray): ( - name: string, - fn: (...args: T[]) => Awaitable, - ) => void -} - -interface TestEachFunction { - (cases: ReadonlyArray): ( - name: string, - fn: (...args: T) => Awaitable, - options?: number | TestOptions, - ) => void - >(cases: ReadonlyArray): ( - name: string, - fn: (...args: ExtractEachCallbackArgs) => Awaitable, - options?: number | TestOptions, - ) => void - (cases: ReadonlyArray): ( - name: string, - fn: (...args: T[]) => Awaitable, - options?: number | TestOptions, - ) => void - (...args: [TemplateStringsArray, ...any]): ( - name: string, - fn: (...args: any[]) => Awaitable, - options?: number | TestOptions, - ) => void -} - -type ChainableTestAPI = ChainableFunction< - 'concurrent' | 'only' | 'skip' | 'todo' | 'fails', - [name: string, fn?: TestFunction, options?: number | TestOptions], - void, - { - each: TestEachFunction - (name: string, fn?: TestFunction, options?: number | TestOptions): void - } -> - -export interface TestOptions { - /** - * Test timeout. - */ - timeout?: number - /** - * Times to retry the test if fails. Useful for making flaky tests more stable. - * When retries is up, the last test error will be thrown. - * - * @default 1 - */ - retry?: number -} - -export type TestAPI = ChainableTestAPI & { - each: TestEachFunction - skipIf(condition: any): ChainableTestAPI - runIf(condition: any): ChainableTestAPI -} - -type ChainableSuiteAPI = ChainableFunction< - 'concurrent' | 'only' | 'skip' | 'todo' | 'shuffle', - [name: string, factory?: SuiteFactory, options?: number | TestOptions], - SuiteCollector, - { - each: TestEachFunction - (name: string, factory?: SuiteFactory): SuiteCollector - } -> - -export type SuiteAPI = ChainableSuiteAPI & { - each: SuiteEachFunction - skipIf(condition: any): ChainableSuiteAPI - runIf(condition: any): ChainableSuiteAPI -} - -export type HookListener = (...args: T) => Awaitable - -export type HookCleanupCallback = (() => Awaitable) | void - -export interface SuiteHooks { - beforeAll: HookListener<[Suite | File], HookCleanupCallback>[] - afterAll: HookListener<[Suite | File]>[] - beforeEach: HookListener<[TestContext & ExtraContext, Suite], HookCleanupCallback>[] - afterEach: HookListener<[TestContext & ExtraContext, Suite]>[] -} - -export interface SuiteCollector { - readonly name: string - readonly mode: RunMode - type: 'collector' - test: TestAPI - benchmark: BenchmarkAPI - tasks: (Suite | Test | Benchmark | SuiteCollector)[] - collect: (file?: File) => Promise - clear: () => void - on: >(name: T, ...fn: SuiteHooks[T]) => void -} - -export type SuiteFactory = (test: (name: string, fn: TestFunction) => void) => Awaitable - -export interface RuntimeContext { - tasks: (SuiteCollector | Test)[] - currentSuite: SuiteCollector | null -} - -export interface TestContext { - /** - * Metadata of the current test - */ - meta: Readonly - - /** - * A expect instance bound to the test - */ - expect: Vi.ExpectStatic - - /** - * Extract hooks on test failed - */ - onTestFailed: (fn: OnTestFailedHandler) => void -} - -export type OnTestFailedHandler = (result: TaskResult) => Awaitable +export { + RunMode, + TaskState, + TaskBase, + TaskResult, + TaskResultPack, + Suite, + File, + Test, + Task, + DoneCallback, + TestFunction, + TestOptions, + TestAPI, + SuiteAPI, + HookListener, + HookCleanupCallback, + SuiteHooks, + SuiteCollector, + SuiteFactory, + RuntimeContext, + TestContext, + OnTestFailedHandler, +} from '@vitest/runner' diff --git a/packages/vitest/src/types/worker.ts b/packages/vitest/src/types/worker.ts index a4eb92dde048..deff9cfaacf3 100644 --- a/packages/vitest/src/types/worker.ts +++ b/packages/vitest/src/types/worker.ts @@ -1,9 +1,9 @@ import type { MessagePort } from 'node:worker_threads' +import type { File, TaskResultPack, Test } from '@vitest/runner' import type { FetchFunction, ModuleCacheMap, RawSourceMap, ViteNodeResolveId } from 'vite-node' import type { BirpcReturn } from 'birpc' import type { MockMap } from './mocker' import type { ResolvedConfig } from './config' -import type { File, TaskResultPack, Test } from './tasks' import type { SnapshotResult } from './snapshot' import type { UserConsoleLog } from './general' @@ -46,6 +46,5 @@ export interface WorkerGlobalState { current?: Test filepath?: string moduleCache: ModuleCacheMap - browserHashMap?: Map mockMap: MockMap } diff --git a/packages/vitest/src/utils/collect.ts b/packages/vitest/src/utils/collect.ts deleted file mode 100644 index 753797b0a0b2..000000000000 --- a/packages/vitest/src/utils/collect.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { Suite, TaskBase } from '../types' - -/** - * If any tasks been marked as `only`, mark all other tasks as `skip`. - */ -export function interpretTaskModes(suite: Suite, namePattern?: string | RegExp, onlyMode?: boolean, parentIsOnly?: boolean, allowOnly?: boolean) { - const suiteIsOnly = parentIsOnly || suite.mode === 'only' - - suite.tasks.forEach((t) => { - // Check if either the parent suite or the task itself are marked as included - const includeTask = suiteIsOnly || t.mode === 'only' - if (onlyMode) { - if (t.type === 'suite' && (includeTask || someTasksAreOnly(t))) { - // Don't skip this suite - if (t.mode === 'only') { - checkAllowOnly(t, allowOnly) - t.mode = 'run' - } - } - else if (t.mode === 'run' && !includeTask) { - t.mode = 'skip' - } - else if (t.mode === 'only') { - checkAllowOnly(t, allowOnly) - t.mode = 'run' - } - } - if (t.type === 'test') { - if (namePattern && !getTaskFullName(t).match(namePattern)) - t.mode = 'skip' - } - else if (t.type === 'suite') { - if (t.mode === 'skip') - skipAllTasks(t) - else - interpretTaskModes(t, namePattern, onlyMode, includeTask, allowOnly) - } - }) - - // if all subtasks are skipped, mark as skip - if (suite.mode === 'run') { - if (suite.tasks.length && suite.tasks.every(i => i.mode !== 'run')) - suite.mode = 'skip' - } -} - -function getTaskFullName(task: TaskBase): string { - return `${task.suite ? `${getTaskFullName(task.suite)} ` : ''}${task.name}` -} - -export function someTasksAreOnly(suite: Suite): boolean { - return suite.tasks.some(t => t.mode === 'only' || (t.type === 'suite' && someTasksAreOnly(t))) -} - -function skipAllTasks(suite: Suite) { - suite.tasks.forEach((t) => { - if (t.mode === 'run') { - t.mode = 'skip' - if (t.type === 'suite') - skipAllTasks(t) - } - }) -} - -function checkAllowOnly(task: TaskBase, allowOnly?: boolean) { - if (allowOnly) - return - const error = new Error('[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error') - task.result = { - state: 'fail', - error, - errors: [error], - } -} - -export function generateHash(str: string): string { - let hash = 0 - if (str.length === 0) - return `${hash}` - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i) - hash = (hash << 5) - hash + char - hash = hash & hash // Convert to 32bit integer - } - return `${hash}` -} - -export function calculateSuiteHash(parent: Suite) { - parent.tasks.forEach((t, idx) => { - t.id = `${parent.id}_${idx}` - if (t.type === 'suite') - calculateSuiteHash(t) - }) -} diff --git a/packages/vitest/src/utils/import.ts b/packages/vitest/src/utils/import.ts index 16dab1312edf..0cefc901d256 100644 --- a/packages/vitest/src/utils/import.ts +++ b/packages/vitest/src/utils/import.ts @@ -1,7 +1,8 @@ import { getWorkerState } from './global' -import { setTimeout } from './timers' +import { getSafeTimers } from './timers' function waitNextTick() { + const { setTimeout } = getSafeTimers() return new Promise(resolve => setTimeout(resolve, 0)) } diff --git a/packages/vitest/src/utils/index.ts b/packages/vitest/src/utils/index.ts index 73365d759e5a..544824559eda 100644 --- a/packages/vitest/src/utils/index.ts +++ b/packages/vitest/src/utils/index.ts @@ -1,12 +1,11 @@ import { relative as relativeBrowser } from 'node:path' import c from 'picocolors' +import type { Suite, Task } from '@vitest/runner' import { isPackageExists } from 'local-pkg' import { relative as relativeNode } from 'pathe' import type { ModuleCacheMap } from 'vite-node' -import type { Suite, Task } from '../types' import { EXIT_CODE_RESTART } from '../constants' import { getWorkerState } from '../utils' -import { getNames } from './tasks' import { isBrowser, isCI, isNode } from './env' export * from './graph' @@ -64,10 +63,6 @@ export function resetModules(modules: ModuleCacheMap, resetMocks = false) { }) } -export function getFullName(task: Task) { - return getNames(task).join(c.dim(' > ')) -} - export function removeUndefinedValues>(obj: T): T { for (const key in Object.keys(obj)) { if (obj[key] === undefined) @@ -164,25 +159,6 @@ class AggregateErrorPonyfill extends Error { } export { AggregateErrorPonyfill as AggregateError } -type DeferPromise = Promise & { - resolve: (value: T | PromiseLike) => void - reject: (reason?: any) => void -} - -export function createDefer(): DeferPromise { - let resolve: ((value: T | PromiseLike) => void) | null = null - let reject: ((reason?: any) => void) | null = null - - const p = new Promise((_resolve, _reject) => { - resolve = _resolve - reject = _reject - }) as DeferPromise - - p.resolve = resolve! - p.reject = reject! - return p -} - export function objectAttr(source: any, path: string, defaultValue = undefined) { // a[3].b -> a.3.b const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.') diff --git a/packages/vitest/src/utils/tasks.ts b/packages/vitest/src/utils/tasks.ts index 70c547567b83..df160708519f 100644 --- a/packages/vitest/src/utils/tasks.ts +++ b/packages/vitest/src/utils/tasks.ts @@ -1,32 +1,12 @@ -import type { Arrayable, Benchmark, Suite, Task, Test } from '../types' +import { getNames, getTests } from '@vitest/runner/utils' +import c from 'picocolors' +import type { Arrayable, Suite, Task } from '../types' import { toArray } from './base' -function isAtomTest(s: Task): s is Test | Benchmark { - return (s.type === 'test' || s.type === 'benchmark') -} - -export function getTests(suite: Arrayable): (Test | Benchmark)[] { - return toArray(suite).flatMap(s => isAtomTest(s) ? [s] : s.tasks.flatMap(c => isAtomTest(c) ? [c] : getTests(c))) -} - -export function getTasks(tasks: Arrayable = []): Task[] { - return toArray(tasks).flatMap(s => isAtomTest(s) ? [s] : [s, ...getTasks(s.tasks)]) -} - -export function getSuites(suite: Arrayable): Suite[] { - return toArray(suite).flatMap(s => s.type === 'suite' ? [s, ...getSuites(s.tasks)] : []) -} - -export function hasTests(suite: Arrayable): boolean { - return toArray(suite).some(s => s.tasks.some(c => isAtomTest(c) || hasTests(c))) -} +export { getTasks, getTests, getSuites, hasTests, hasFailed } from '@vitest/runner/utils' export function hasBenchmark(suite: Arrayable): boolean { - return toArray(suite).some(s => s?.tasks?.some(c => c.type === 'benchmark' || hasBenchmark(c as Suite))) -} - -export function hasFailed(suite: Arrayable): boolean { - return toArray(suite).some(s => s.result?.state === 'fail' || (s.type === 'suite' && hasFailed(s.tasks))) + return toArray(suite).some(s => s?.tasks?.some(c => c.meta?.benchmark || hasBenchmark(c as Suite))) } export function hasFailedSnapshot(suite: Arrayable): boolean { @@ -35,15 +15,6 @@ export function hasFailedSnapshot(suite: Arrayable): boolean { }) } -export function getNames(task: Task) { - const names = [task.name] - let current: Task | undefined = task - - while (current?.suite || current?.file) { - current = current.suite || current.file - if (current?.name) - names.unshift(current.name) - } - - return names +export function getFullName(task: Task) { + return getNames(task).join(c.dim(' > ')) } diff --git a/packages/vitest/src/utils/timers.ts b/packages/vitest/src/utils/timers.ts index 55b0fd03579c..5e446ab0f285 100644 --- a/packages/vitest/src/utils/timers.ts +++ b/packages/vitest/src/utils/timers.ts @@ -1,13 +1,3 @@ -const { - setTimeout: safeSetTimeout, - setInterval: safeSetInterval, - clearInterval: safeClearInterval, - clearTimeout: safeClearTimeout, -} = globalThis - export { - safeSetTimeout as setTimeout, - safeSetInterval as setInterval, - safeClearInterval as clearInterval, - safeClearTimeout as clearTimeout, -} + getSafeTimers, +} from '@vitest/utils' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05040a2eb7fc..3821fe20301c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,7 @@ importers: '@vitest/browser': workspace:* '@vitest/coverage-c8': workspace:* '@vitest/coverage-istanbul': workspace:* + '@vitest/runner': workspace:^0.27.2 '@vitest/ui': workspace:* bumpp: ^8.2.1 c8: ^7.12.0 @@ -63,6 +64,7 @@ importers: '@vitest/browser': link:packages/browser '@vitest/coverage-c8': link:packages/coverage-c8 '@vitest/coverage-istanbul': link:packages/coverage-istanbul + '@vitest/runner': link:packages/runner '@vitest/ui': link:packages/ui bumpp: 8.2.1 c8: 7.12.0 @@ -848,6 +850,7 @@ importers: '@types/prompts': ^2.4.2 '@types/sinonjs__fake-timers': ^8.1.2 '@vitest/expect': workspace:* + '@vitest/runner': workspace:^0.27.2 '@vitest/spy': workspace:* '@vitest/ui': workspace:* '@vitest/utils': workspace:* @@ -898,6 +901,11 @@ importers: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.7.13 + '@vitest/expect': link:../expect + '@vitest/runner': link:../runner + '@vitest/spy': link:../spy + '@vitest/ui': link:../ui + '@vitest/utils': link:../utils acorn: 8.8.1 acorn-walk: 8.2.0 cac: 6.7.14 @@ -924,10 +932,6 @@ importers: '@types/natural-compare': 1.4.1 '@types/prompts': 2.4.2 '@types/sinonjs__fake-timers': 8.1.2 - '@vitest/expect': link:../expect - '@vitest/spy': link:../spy - '@vitest/ui': link:../ui - '@vitest/utils': link:../utils birpc: 0.2.3 chai-subset: 1.6.0 cli-truncate: 3.1.0 @@ -1036,10 +1040,16 @@ importers: test/core: specifiers: + '@vitest/expect': workspace:^0.27.2 + '@vitest/runner': workspace:* + '@vitest/utils': workspace:* tinyspy: ^1.0.2 url: ^0.11.0 vitest: workspace:* devDependencies: + '@vitest/expect': link:../../packages/expect + '@vitest/runner': link:../../packages/runner + '@vitest/utils': link:../../packages/utils tinyspy: 1.0.2 url: 0.11.0 vitest: link:../../packages/vitest diff --git a/test/core/package.json b/test/core/package.json index 58f30d382ef8..7d207920e065 100644 --- a/test/core/package.json +++ b/test/core/package.json @@ -6,6 +6,9 @@ "coverage": "vitest run --coverage" }, "devDependencies": { + "@vitest/expect": "workspace:^0.27.2", + "@vitest/runner": "workspace:*", + "@vitest/utils": "workspace:*", "tinyspy": "^1.0.2", "url": "^0.11.0", "vitest": "workspace:*" diff --git a/test/core/test/chainable.test.ts b/test/core/test/chainable.test.ts index ac7148f7b18e..d5f880f7c308 100644 --- a/test/core/test/chainable.test.ts +++ b/test/core/test/chainable.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { createChainable } from '../../../packages/vitest/src/runtime/chain' +import { createChainable } from '@vitest/runner/utils' describe('chainable', () => { it('creates', () => { diff --git a/test/core/test/replace-matcher.test.ts b/test/core/test/replace-matcher.test.ts index c4aa53e9a94e..d6e21c4f6c52 100644 --- a/test/core/test/replace-matcher.test.ts +++ b/test/core/test/replace-matcher.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { replaceAsymmetricMatcher } from '../../../packages/vitest/src/runtime/error' +import { replaceAsymmetricMatcher } from '@vitest/runner/utils' describe('replace asymmetric matcher', () => { const expectReplaceAsymmetricMatcher = (actual: any, expected: any) => { diff --git a/test/core/test/serialize.test.ts b/test/core/test/serialize.test.ts index 6179c8cddb8d..325d9add9a6f 100644 --- a/test/core/test/serialize.test.ts +++ b/test/core/test/serialize.test.ts @@ -1,7 +1,7 @@ // @vitest-environment jsdom import { describe, expect, it } from 'vitest' -import { serializeError } from '../../../packages/vitest/src/runtime/error' +import { serializeError } from '@vitest/runner/utils' describe('error serialize', () => { it('works', () => { diff --git a/test/core/vitest.config.ts b/test/core/vitest.config.ts index fa07e6ba8474..c1f21c8e9014 100644 --- a/test/core/vitest.config.ts +++ b/test/core/vitest.config.ts @@ -35,7 +35,7 @@ export default defineConfig({ alias: [ { find: '#', replacement: resolve(__dirname, 'src') }, { find: '$', replacement: 'src' }, - { find: '@vitest', replacement: resolve(__dirname, '..', '..', 'packages') }, + // { find: '@vitest', replacement: resolve(__dirname, '..', '..', 'packages') }, ], }, test: { diff --git a/tsconfig.json b/tsconfig.json index ebe03fc547a8..2fd63dcc7452 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,8 @@ "@vitest/utils": ["./packages/utils/src/index.ts"], "@vitest/spy": ["./packages/spy/src/index.ts"], "@vitest/expect": ["./packages/expect/src/index.ts"], + "@vitest/runner": ["./packages/runner/src/index.ts"], + "@vitest/runner/utils": ["./packages/runner/src/utils/index.ts"], "@vitest/browser": ["./packages/browser/src/node/index.ts"], "#types": ["./packages/vitest/src/index.ts"], "~/*": ["./packages/ui/client/*"], From 8fca1704552be2e4362c1bf8cd86e6a5781df46b Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 14:51:56 +0100 Subject: [PATCH 05/47] chore: don't inline vitest --- packages/vitest/src/node/config.ts | 6 +++--- packages/vitest/src/runtime/execute.ts | 10 +++++++--- packages/vitest/src/types/tasks.ts | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 7ff6540be997..f6fae9fd01a8 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -18,11 +18,11 @@ const extraInlineDeps = [ // Vite client /vite\w*\/dist\/client\/env.mjs/, // Vitest - /\/vitest\/dist\//, + /\/vitest\/dist\/entry\.js/, // yarn's .store folder - /vitest-virtual-\w+\/dist/, + /vitest-virtual-\w+\/dist\/entry\.js/, // cnpm - /@vitest\/dist/, + /@vitest\/dist\/entry\.js/, // Nuxt '@nuxt/test-utils', ] diff --git a/packages/vitest/src/runtime/execute.ts b/packages/vitest/src/runtime/execute.ts index de3c5bffe4b3..3875ffb4fec8 100644 --- a/packages/vitest/src/runtime/execute.ts +++ b/packages/vitest/src/runtime/execute.ts @@ -31,6 +31,12 @@ export class VitestRunner extends ViteNodeRunner { super(options) this.mocker = new VitestMocker(this) + + Object.defineProperty(globalThis, '__vitest_mocker__', { + value: this.mocker, + writable: true, + configurable: true, + }) } shouldResolveId(id: string, _importee?: string | undefined): boolean { @@ -67,9 +73,7 @@ export class VitestRunner extends ViteNodeRunner { Object.defineProperty(context.__vite_ssr_import_meta__, 'vitest', { get: () => globalThis.__vitest_index__ }) } - return Object.assign(context, { - __vitest_mocker__: this.mocker, - }) + return context } shouldInterop(path: string, mod: any) { diff --git a/packages/vitest/src/types/tasks.ts b/packages/vitest/src/types/tasks.ts index 9ce97e435e90..a078e109da52 100644 --- a/packages/vitest/src/types/tasks.ts +++ b/packages/vitest/src/types/tasks.ts @@ -1,4 +1,4 @@ -export { +export type { RunMode, TaskState, TaskBase, From 2c44c4023fc154de37aba0b7b4d52f377c1dfe61 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 14:52:04 +0100 Subject: [PATCH 06/47] chore: fix lockfile --- pnpm-lock.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3821fe20301c..ae17d527caf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,7 +23,6 @@ importers: '@vitest/browser': workspace:* '@vitest/coverage-c8': workspace:* '@vitest/coverage-istanbul': workspace:* - '@vitest/runner': workspace:^0.27.2 '@vitest/ui': workspace:* bumpp: ^8.2.1 c8: ^7.12.0 @@ -64,7 +63,6 @@ importers: '@vitest/browser': link:packages/browser '@vitest/coverage-c8': link:packages/coverage-c8 '@vitest/coverage-istanbul': link:packages/coverage-istanbul - '@vitest/runner': link:packages/runner '@vitest/ui': link:packages/ui bumpp: 8.2.1 c8: 7.12.0 @@ -850,7 +848,7 @@ importers: '@types/prompts': ^2.4.2 '@types/sinonjs__fake-timers': ^8.1.2 '@vitest/expect': workspace:* - '@vitest/runner': workspace:^0.27.2 + '@vitest/runner': workspace:* '@vitest/spy': workspace:* '@vitest/ui': workspace:* '@vitest/utils': workspace:* From eb4e5f782413b23b67b4dd6fb41d13d6477bdcb0 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 15:03:26 +0100 Subject: [PATCH 07/47] feat: allow custom test runners --- docs/config/index.md | 7 +++++++ packages/browser/src/client/runner.ts | 20 ++++++++++++++++++++ packages/runner/src/suite.ts | 1 + packages/runner/src/types/runner.ts | 4 ++++ packages/vitest/src/node/config.ts | 5 +++++ packages/vitest/src/runtime/entry.ts | 13 +++++++++---- packages/vitest/src/types/config.ts | 8 +++++++- 7 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 packages/browser/src/client/runner.ts diff --git a/docs/config/index.md b/docs/config/index.md index 68a56780756d..4a6c2a58ae0f 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -1053,6 +1053,13 @@ Options to configure Vitest cache policy. At the moment Vitest stores cache for Path to cache directory. +### runner + +- **Type**: `VitestRunnerConstructor` +- **Default**: `node`, when running tests, or `benchmark`, when running benchmarks + +Path to custom test runner. + ### sequence - **Type**: `{ sequencer?, shuffle?, seed?, hooks? }` diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts new file mode 100644 index 000000000000..266a6883b76c --- /dev/null +++ b/packages/browser/src/client/runner.ts @@ -0,0 +1,20 @@ +import type { VitestRunner } from '@vitest/runner' +import type { ResolvedConfig } from '#types' + +// TODO +export class BrowserTestRunner implements VitestRunner { + hasMap = new Map() + + constructor(public config: ResolvedConfig) { + this.config = config + } + + async importFile(filepath: string) { + const match = filepath.match(/^(\w:\/)/) + const hash = this.hasMap.get(filepath) + if (match) + return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) + else + return await import(`${filepath}?v=${hash}`) + } +} diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index 2058d2cefc57..c7b753068f31 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -95,6 +95,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m tasks.push(test) }) + // TODO: document how it can be used to extend native runner const custom = function (name: string) { const task: TaskCustom = { id: '', diff --git a/packages/runner/src/types/runner.ts b/packages/runner/src/types/runner.ts index ccb141f26736..a5b9a66629e0 100644 --- a/packages/runner/src/types/runner.ts +++ b/packages/runner/src/types/runner.ts @@ -17,6 +17,10 @@ export interface VitestRunnerConfig { hookTimeout: number } +export interface VitestRunnerConstructor { + new (config: VitestRunnerConfig): VitestRunner +} + export interface VitestRunner { onBeforeCollect?(): unknown onCollected?(files: File[]): unknown diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index f6fae9fd01a8..3fa51e6a6179 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -129,6 +129,11 @@ export function resolveConfig( } } + if (resolved.runner) { + resolved.runner = resolveModule(resolved.runner, { paths: [resolved.root] }) + ?? resolve(resolved.root, resolved.runner) + } + // disable loader for Yarn PnP until Node implements chain loader // https://github.com/nodejs/node/pull/43772 resolved.deps.registerNodeLoader ??= false diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index 5d90987572b8..8ae421f8ab5a 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -1,5 +1,6 @@ import { promises as fs } from 'node:fs' import mm from 'micromatch' +import type { VitestRunnerConstructor } from '@vitest/runner' import { startTests } from '@vitest/runner' import type { EnvironmentOptions, ResolvedConfig, VitestEnvironment } from '../types' import { getWorkerState, resetModules } from '../utils' @@ -18,16 +19,20 @@ function groupBy(collection: T[], iterate }, {} as Record) } +async function getTestRunner(config: ResolvedConfig): Promise { + if (config.runner) + return await import(config.runner) + return (config.mode === 'test' ? NodeTestRunner : NodeBenchmarkRunner) as any as VitestRunnerConstructor +} + // browser shouldn't call this! export async function run(files: string[], config: ResolvedConfig): Promise { await setupGlobalEnv(config) const workerState = getWorkerState() - // TODO: allow custom runners? - const testRunner = config.mode === 'test' - ? new NodeTestRunner(config) - : new NodeBenchmarkRunner(config) + const TestRunner = await getTestRunner(config) + const testRunner = new TestRunner(config) // if calling from a worker, there will always be one file // if calling with no-threads, this will be the whole suite diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index 1cf90d4ccf4f..376654d606ec 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -514,6 +514,11 @@ export interface InlineConfig { * @default 300 */ slowTestThreshold?: number + + /** + * Path to a custom test runner. + */ + runner?: string } export interface TypecheckConfig { @@ -584,7 +589,7 @@ export interface UserConfig extends InlineConfig { shard?: string } -export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck'> { +export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner'> { mode: VitestRunMode base?: string @@ -624,6 +629,7 @@ export interface ResolvedConfig extends Omit, 'config' | 'f } typecheck: TypecheckConfig + runner?: string } export type RuntimeConfig = Pick< From 06fd22a420b0699309ef7919b24911f8a8a68e83 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 15:06:32 +0100 Subject: [PATCH 08/47] chore: move things around --- .../vitest/src/runtime/runners/browser.ts | 20 ------------------- packages/ws-client/package.json | 1 + pnpm-lock.yaml | 2 ++ 3 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 packages/vitest/src/runtime/runners/browser.ts diff --git a/packages/vitest/src/runtime/runners/browser.ts b/packages/vitest/src/runtime/runners/browser.ts deleted file mode 100644 index 006adfbb7a3e..000000000000 --- a/packages/vitest/src/runtime/runners/browser.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { VitestRunner } from '@vitest/runner' -import type { ResolvedConfig } from '#types' - -// TODO should be inside browser package -export class BrowserTestRunner implements VitestRunner { - hasMap = new Map() - - constructor(public config: ResolvedConfig) { - this.config = config - } - - async importFile(filepath: string) { - const match = filepath.match(/^(\w:\/)/) - const hash = this.hasMap.get(filepath) - if (match) - return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) - else - return await import(`${filepath}?v=${hash}`) - } -} diff --git a/packages/ws-client/package.json b/packages/ws-client/package.json index 55213c9b9650..1c9062d26d01 100644 --- a/packages/ws-client/package.json +++ b/packages/ws-client/package.json @@ -44,6 +44,7 @@ "ws": "^8.12.0" }, "devDependencies": { + "@vitest/runner": "workspace:^0.27.2", "rollup": "^2.79.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae17d527caf9..a32a1a75ed24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -977,6 +977,7 @@ importers: packages/ws-client: specifiers: + '@vitest/runner': workspace:^0.27.2 birpc: ^0.2.3 flatted: ^3.2.7 rollup: ^2.79.1 @@ -986,6 +987,7 @@ importers: flatted: 3.2.7 ws: 8.12.0 devDependencies: + '@vitest/runner': link:../runner rollup: 2.79.1 test/base: From ec415c8b2eca81de5772c86559431442473c8009 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 15:14:20 +0100 Subject: [PATCH 09/47] chore: cleanup --- packages/vitest/src/runtime/entry.ts | 3 ++- packages/vitest/src/utils/tasks.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index 8ae421f8ab5a..3fe5e40bc296 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -21,7 +21,8 @@ function groupBy(collection: T[], iterate async function getTestRunner(config: ResolvedConfig): Promise { if (config.runner) - return await import(config.runner) + // TODO: validation + return (await import(config.runner)).default return (config.mode === 'test' ? NodeTestRunner : NodeBenchmarkRunner) as any as VitestRunnerConstructor } diff --git a/packages/vitest/src/utils/tasks.ts b/packages/vitest/src/utils/tasks.ts index df160708519f..518d2ca304db 100644 --- a/packages/vitest/src/utils/tasks.ts +++ b/packages/vitest/src/utils/tasks.ts @@ -3,6 +3,8 @@ import c from 'picocolors' import type { Arrayable, Suite, Task } from '../types' import { toArray } from './base' +export { getNames } from '@vitest/runner/utils' + export { getTasks, getTests, getSuites, hasTests, hasFailed } from '@vitest/runner/utils' export function hasBenchmark(suite: Arrayable): boolean { From 2f2e90701f74561968fa7347279a038c69e62fad Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 15:33:16 +0100 Subject: [PATCH 10/47] chore: fix benchmark --- packages/browser/src/client/main.ts | 2 +- packages/runner/src/suite.ts | 4 ++-- packages/vitest/src/node/reporters/renderers/utils.ts | 2 +- packages/vitest/src/runtime/benchmark.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/browser/src/client/main.ts b/packages/browser/src/client/main.ts index 9feb59d8967f..bda9e22ac132 100644 --- a/packages/browser/src/client/main.ts +++ b/packages/browser/src/client/main.ts @@ -77,7 +77,7 @@ ws.addEventListener('open', async () => { async function runTests(paths: string[], config: any, client: VitestClient) { const name = '/__vitest_index__' - const { startTests, setupGlobalEnv } = (await import(name)) as unknown as typeof import('vitest/browser') + const { startTests, setupGlobalEnv } = (await import(name)) as unknown as typeof import('vitest') await setupGlobalEnv(config as any) diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index c7b753068f31..c2c0c544dcfe 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -96,12 +96,12 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m }) // TODO: document how it can be used to extend native runner - const custom = function (name: string) { + const custom = function (this: Record, name: string) { const task: TaskCustom = { id: '', name, type: 'custom', - mode: 'run', + mode: this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run', } tasks.push(task) return task diff --git a/packages/vitest/src/node/reporters/renderers/utils.ts b/packages/vitest/src/node/reporters/renderers/utils.ts index 8d0313677da6..bb31e14ec173 100644 --- a/packages/vitest/src/node/reporters/renderers/utils.ts +++ b/packages/vitest/src/node/reporters/renderers/utils.ts @@ -132,7 +132,7 @@ export function getStateSymbol(task: Task) { } if (task.result.state === 'pass') { - return task.type === 'benchmark' + return task.meta?.benchmark ? c.green(F_DOT) : c.green(F_CHECK) } diff --git a/packages/vitest/src/runtime/benchmark.ts b/packages/vitest/src/runtime/benchmark.ts index d5e6d00e92ee..5196a156046f 100644 --- a/packages/vitest/src/runtime/benchmark.ts +++ b/packages/vitest/src/runtime/benchmark.ts @@ -16,8 +16,8 @@ export function getBenchFn(key: TaskCustom): BenchFunction { } export const bench = createBenchmark( - (name, fn: BenchFunction = noop, options: BenchOptions = {}) => { - const task = getCurrentSuite().custom(name) + function (name, fn: BenchFunction = noop, options: BenchOptions = {}) { + const task = getCurrentSuite().custom.call(this, name) task.meta = { benchmark: true, } From d1b0a1256c2385eddb5d4d14ad634fcc5c9b56ae Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 17:42:59 +0100 Subject: [PATCH 11/47] chore: cleanup --- packages/browser/package.json | 1 + packages/browser/src/client/main.ts | 11 +- packages/browser/src/client/runner.ts | 22 ++-- packages/browser/src/node/index.ts | 6 - packages/expect/src/jest-expect.ts | 3 +- packages/expect/src/jest-matcher-utils.ts | 9 +- packages/ui/package.json | 1 + packages/utils/diff.d.ts | 1 + packages/utils/helpers.d.ts | 1 + packages/utils/src/diff.ts | 16 ++- packages/utils/src/index.ts | 1 - packages/vitest/src/node/core.ts | 4 + packages/vitest/src/node/error.ts | 6 +- packages/vitest/src/utils/diff.ts | 128 ---------------------- packages/vitest/src/utils/source-map.ts | 4 +- packages/vitest/src/utils/tasks.ts | 5 +- packages/ws-client/package.json | 2 +- pnpm-lock.yaml | 4 + test/core/test/diff.test.ts | 10 +- tsconfig.json | 3 +- 20 files changed, 69 insertions(+), 169 deletions(-) create mode 100644 packages/utils/diff.d.ts create mode 100644 packages/utils/helpers.d.ts delete mode 100644 packages/vitest/src/utils/diff.ts diff --git a/packages/browser/package.json b/packages/browser/package.json index 9b1d6f8a9139..cdbc412490fd 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -34,6 +34,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { + "@vitest/runner": "workspace:*", "local-pkg": "^0.4.2", "mlly": "^1.1.0", "modern-node-polyfills": "0.0.9", diff --git a/packages/browser/src/client/main.ts b/packages/browser/src/client/main.ts index bda9e22ac132..f86a965d45e8 100644 --- a/packages/browser/src/client/main.ts +++ b/packages/browser/src/client/main.ts @@ -2,6 +2,8 @@ import type { VitestClient } from '@vitest/ws-client' import { createClient } from '@vitest/ws-client' // eslint-disable-next-line no-restricted-imports import type { ResolvedConfig } from 'vitest' +import type { VitestRunner } from '@vitest/runner' +import { BrowserTestRunner } from './runner' // @ts-expect-error mocking some node apis globalThis.process = { env: {}, argv: [], stdout: { write: () => {} } } @@ -14,6 +16,7 @@ export const ENTRY_URL = `${ }//${HOST}/__vitest_api__` let config: ResolvedConfig | undefined +let runner: VitestRunner | undefined const browserHashMap = new Map() export const client = createClient(ENTRY_URL, { @@ -76,12 +79,12 @@ ws.addEventListener('open', async () => { }) async function runTests(paths: string[], config: any, client: VitestClient) { - const name = '/__vitest_index__' - const { startTests, setupGlobalEnv } = (await import(name)) as unknown as typeof import('vitest') + const { startTests } = await import('@vitest/runner') - await setupGlobalEnv(config as any) + if (!runner) + runner = new BrowserTestRunner({ config, client, browserHashMap }) - await startTests(paths, config as any) + await startTests(paths, runner) await client.rpc.onFinished() await client.rpc.onWatcherStart() diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts index 266a6883b76c..b74ea8852c19 100644 --- a/packages/browser/src/client/runner.ts +++ b/packages/browser/src/client/runner.ts @@ -1,20 +1,28 @@ import type { VitestRunner } from '@vitest/runner' +import type { VitestClient } from '@vitest/ws-client' import type { ResolvedConfig } from '#types' -// TODO +interface BrowserRunnerOptions { + config: ResolvedConfig + client: VitestClient + browserHashMap: Map +} + export class BrowserTestRunner implements VitestRunner { + public config: ResolvedConfig hasMap = new Map() - constructor(public config: ResolvedConfig) { - this.config = config + constructor(options: BrowserRunnerOptions) { + this.config = options.config + this.hasMap = options.browserHashMap } async importFile(filepath: string) { const match = filepath.match(/^(\w:\/)/) const hash = this.hasMap.get(filepath) - if (match) - return await import(`/@fs/${filepath.slice(match[1].length)}?v=${hash}`) - else - return await import(`${filepath}?v=${hash}`) + const importpath = match + ? `/@fs/${filepath.slice(match[1].length)}?v=${hash}` + : `${filepath}?v=${hash}` + await import(importpath) } } diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index 89fb2fa8e9c6..859c299d04b7 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -5,7 +5,6 @@ import { builtinModules } from 'module' import { polyfillPath } from 'modern-node-polyfills' import sirv from 'sirv' import type { Plugin } from 'vite' -import { resolvePath } from 'mlly' const stubs = [ 'fs', @@ -36,11 +35,6 @@ export default (base = '/'): Plugin[] => { if (ctx.ssr) return - if (id === '/__vitest_index__') { - const result = await resolvePath('vitest/browser') - return result - } - if (stubs.includes(id)) return resolve(pkgRoot, 'stubs', id) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index e57f6e9c574f..28e70036cd60 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1,6 +1,7 @@ import c from 'picocolors' import { AssertionError } from 'chai' -import { assertTypes, unifiedDiff } from '@vitest/utils' +import { assertTypes } from '@vitest/utils' +import { unifiedDiff } from '@vitest/utils/diff' import type { Constructable } from '@vitest/utils' import type { EnhancedSpy } from '@vitest/spy' import { isMockFunction } from '@vitest/spy' diff --git a/packages/expect/src/jest-matcher-utils.ts b/packages/expect/src/jest-matcher-utils.ts index 21902c5c2d0b..d64cffb1beec 100644 --- a/packages/expect/src/jest-matcher-utils.ts +++ b/packages/expect/src/jest-matcher-utils.ts @@ -1,5 +1,6 @@ import c from 'picocolors' -import { stringify, unifiedDiff } from '@vitest/utils' +import { stringify } from '@vitest/utils' +import { unifiedDiff } from '@vitest/utils/diff' import type { DiffOptions, MatcherHintOptions } from './types' export { stringify } @@ -89,5 +90,9 @@ export const printExpected = (value: unknown): string => // TODO: do something with options // eslint-disable-next-line @typescript-eslint/no-unused-vars export function diff(a: any, b: any, options?: DiffOptions) { - return unifiedDiff(stringify(b), stringify(a)) + return unifiedDiff(stringify(b), stringify(a), { + colorDim: c.dim, + colorSuccess: c.green, + colorError: c.red, + }) } diff --git a/packages/ui/package.json b/packages/ui/package.json index 1eeb42928571..df57fce1b73e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -53,6 +53,7 @@ "@unocss/reset": "^0.48.3", "@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue-jsx": "^3.0.0", + "@vitest/runner": "workspace:*", "@vitest/ws-client": "workspace:*", "@vueuse/core": "^9.10.0", "ansi-to-html": "^0.7.2", diff --git a/packages/utils/diff.d.ts b/packages/utils/diff.d.ts new file mode 100644 index 000000000000..0a66b86595c0 --- /dev/null +++ b/packages/utils/diff.d.ts @@ -0,0 +1 @@ +export * from './dist/diff.js' diff --git a/packages/utils/helpers.d.ts b/packages/utils/helpers.d.ts new file mode 100644 index 000000000000..0add1d0f2bd1 --- /dev/null +++ b/packages/utils/helpers.d.ts @@ -0,0 +1 @@ +export * from './dist/helpers.js' diff --git a/packages/utils/src/diff.ts b/packages/utils/src/diff.ts index 1b720554c974..5a2cc5cc0a2c 100644 --- a/packages/utils/src/diff.ts +++ b/packages/utils/src/diff.ts @@ -1,4 +1,3 @@ -import c from 'picocolors' import * as diff from 'diff' import cliTruncate from 'cli-truncate' @@ -6,12 +5,17 @@ export function formatLine(line: string, outputTruncateLength?: number) { return cliTruncate(line, (outputTruncateLength ?? (process.stdout?.columns || 80)) - 4) } +type Color = (str: string) => string + export interface DiffOptions { - noColor?: boolean outputDiffMaxLines?: number outputTruncateLength?: number outputDiffLines?: number showLegend?: boolean + + colorSuccess?: Color + colorError?: Color + colorDim?: Color } /** @@ -27,7 +31,7 @@ export function unifiedDiff(actual: string, expected: string, options: DiffOptio if (actual === expected) return '' - const { outputTruncateLength, outputDiffLines, outputDiffMaxLines, noColor, showLegend = true } = options + const { outputTruncateLength, outputDiffLines, outputDiffMaxLines, showLegend = true } = options const indent = ' ' const diffLimit = outputDiffLines || 15 @@ -41,9 +45,9 @@ export function unifiedDiff(actual: string, expected: string, options: DiffOptio let previousCount = 0 const str = (str: string) => str - const dim = noColor ? str : c.dim - const green = noColor ? str : c.green - const red = noColor ? str : c.red + const dim = options.colorDim || str + const green = options.colorSuccess || str + const red = options.colorError || str function preprocess(line: string) { if (!line || line.match(/\\ No newline/)) return diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index db6092a84cce..e91254aa2a84 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,4 +1,3 @@ -export * from './diff' export * from './helpers' export * from './types' export * from './stringify' diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 45274fd7347a..2b62b29a4d99 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -313,6 +313,10 @@ export class Vitest { } async runFiles(paths: string[]) { + // TODO: support browser + if (this.config.browser) + return + paths = Array.from(new Set(paths)) // previous run diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index d7b27e53d094..2514f301c048 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -3,12 +3,12 @@ import { existsSync, readFileSync } from 'fs' import { normalize, relative } from 'pathe' import c from 'picocolors' import cliTruncate from 'cli-truncate' +import { type DiffOptions, unifiedDiff } from '@vitest/utils/diff' import { stringify } from '@vitest/utils' import type { ErrorWithDiff, ParsedStack } from '../types' import { lineSplitRE, parseStacktrace, positionToOffset } from '../utils/source-map' import { F_POINTER } from '../utils/figures' import { TypeCheckError } from '../typecheck/typechecker' -import { type DiffOptions, unifiedDiff } from '../utils/diff' import type { Vitest } from './core' import { divider } from './reporters/renderers/utils' import type { Logger } from './logger' @@ -165,8 +165,8 @@ function handleImportOutsideModuleError(stack: string, ctx: Vitest) { export function displayDiff(actual: string, expected: string, console: Console, options: Omit = {}) { const diff = unifiedDiff(actual, expected, options) - const dim = options.noColor ? (s: string) => s : c.dim - const black = options.noColor ? (s: string) => s : c.black + const dim = options.colorDim || c.dim + const black = options.colorDim ? c.black : (str: string) => str if (diff) console.error(diff + '\n') else if (actual && expected && actual !== '"undefined"' && expected !== '"undefined"') diff --git a/packages/vitest/src/utils/diff.ts b/packages/vitest/src/utils/diff.ts deleted file mode 100644 index 1b720554c974..000000000000 --- a/packages/vitest/src/utils/diff.ts +++ /dev/null @@ -1,128 +0,0 @@ -import c from 'picocolors' -import * as diff from 'diff' -import cliTruncate from 'cli-truncate' - -export function formatLine(line: string, outputTruncateLength?: number) { - return cliTruncate(line, (outputTruncateLength ?? (process.stdout?.columns || 80)) - 4) -} - -export interface DiffOptions { - noColor?: boolean - outputDiffMaxLines?: number - outputTruncateLength?: number - outputDiffLines?: number - showLegend?: boolean -} - -/** -* Returns unified diff between two strings with coloured ANSI output. -* -* @private -* @param {String} actual -* @param {String} expected -* @return {string} The diff. -*/ - -export function unifiedDiff(actual: string, expected: string, options: DiffOptions = {}) { - if (actual === expected) - return '' - - const { outputTruncateLength, outputDiffLines, outputDiffMaxLines, noColor, showLegend = true } = options - - const indent = ' ' - const diffLimit = outputDiffLines || 15 - const diffMaxLines = outputDiffMaxLines || 50 - - const counts = { - '+': 0, - '-': 0, - } - let previousState: '-' | '+' | null = null - let previousCount = 0 - - const str = (str: string) => str - const dim = noColor ? str : c.dim - const green = noColor ? str : c.green - const red = noColor ? str : c.red - function preprocess(line: string) { - if (!line || line.match(/\\ No newline/)) - return - - const char = line[0] as '+' | '-' - if ('-+'.includes(char)) { - if (previousState !== char) { - previousState = char - previousCount = 0 - } - previousCount++ - counts[char]++ - if (previousCount === diffLimit) - return dim(`${char} ...`) - else if (previousCount > diffLimit) - return - } - return line - } - - const msg = diff.createPatch('string', expected, actual) - let lines = msg.split('\n').slice(5).map(preprocess).filter(Boolean) as string[] - let moreLines = 0 - const isCompact = counts['+'] === 1 && counts['-'] === 1 && lines.length === 2 - - if (lines.length > diffMaxLines) { - const firstDiff = lines.findIndex(line => line[0] === '-' || line[0] === '+') - const displayLines = lines.slice(firstDiff - 2, diffMaxLines) - const lastDisplayedIndex = firstDiff - 2 + diffMaxLines - if (lastDisplayedIndex < lines.length) - moreLines = lines.length - lastDisplayedIndex - lines = displayLines - } - - let formatted = lines.map((line: string) => { - line = line.replace(/\\"/g, '"') - if (line[0] === '-') { - line = formatLine(line.slice(1), outputTruncateLength) - if (isCompact) - return green(line) - return green(`- ${formatLine(line, outputTruncateLength)}`) - } - if (line[0] === '+') { - line = formatLine(line.slice(1), outputTruncateLength) - if (isCompact) - return red(line) - return red(`+ ${formatLine(line, outputTruncateLength)}`) - } - if (line.match(/@@/)) - return '--' - return ` ${line}` - }) - - if (moreLines) - formatted.push(dim(`... ${moreLines} more lines`)) - - if (showLegend) { - // Compact mode - if (isCompact) { - formatted = [ - `${green('- Expected')} ${formatted[0]}`, - `${red('+ Received')} ${formatted[1]}`, - ] - } - else { - if (formatted[0].includes('"')) - formatted[0] = formatted[0].replace('"', '') - - const last = formatted.length - 1 - if (formatted[last].endsWith('"')) - formatted[last] = formatted[last].slice(0, formatted[last].length - 1) - - formatted.unshift( - green(`- Expected - ${counts['-']}`), - red(`+ Received + ${counts['+']}`), - '', - ) - } - } - - return formatted.map(i => i ? (indent + i) : i).join('\n') -} diff --git a/packages/vitest/src/utils/source-map.ts b/packages/vitest/src/utils/source-map.ts index f071a1395b3f..da40409f7e4a 100644 --- a/packages/vitest/src/utils/source-map.ts +++ b/packages/vitest/src/utils/source-map.ts @@ -6,6 +6,8 @@ export const lineSplitRE = /\r?\n/ const stackIgnorePatterns = [ 'node:internal', + /\/packages\/\w+\/dist\//, + /\/@vitest\/\w+\/dist\//, '/vitest/dist/', '/vitest/src/', '/vite-node/dist/', @@ -86,7 +88,7 @@ export function parseStacktrace(e: ErrorWithDiff, full = false): ParsedStack[] { .map((raw): ParsedStack | null => { const stack = parseSingleStack(raw) - if (!stack || (!full && stackIgnorePatterns.some(p => stack.file.includes(p)))) + if (!stack || (!full && stackIgnorePatterns.some(p => stack.file.match(p)))) return null return stack diff --git a/packages/vitest/src/utils/tasks.ts b/packages/vitest/src/utils/tasks.ts index 518d2ca304db..c504b82834f6 100644 --- a/packages/vitest/src/utils/tasks.ts +++ b/packages/vitest/src/utils/tasks.ts @@ -1,5 +1,4 @@ import { getNames, getTests } from '@vitest/runner/utils' -import c from 'picocolors' import type { Arrayable, Suite, Task } from '../types' import { toArray } from './base' @@ -17,6 +16,6 @@ export function hasFailedSnapshot(suite: Arrayable): boolean { }) } -export function getFullName(task: Task) { - return getNames(task).join(c.dim(' > ')) +export function getFullName(task: Task, separator = ' > ') { + return getNames(task).join(separator) } diff --git a/packages/ws-client/package.json b/packages/ws-client/package.json index 1c9062d26d01..a8ca547969bd 100644 --- a/packages/ws-client/package.json +++ b/packages/ws-client/package.json @@ -44,7 +44,7 @@ "ws": "^8.12.0" }, "devDependencies": { - "@vitest/runner": "workspace:^0.27.2", + "@vitest/runner": "workspace:*", "rollup": "^2.79.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a32a1a75ed24..b20eab8897cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -626,6 +626,7 @@ importers: packages/browser: specifiers: '@types/ws': ^8.5.4 + '@vitest/runner': workspace:^0.27.2 '@vitest/ws-client': workspace:* local-pkg: ^0.4.2 mlly: ^1.1.0 @@ -635,6 +636,7 @@ importers: sirv: ^2.0.2 vitest: workspace:* dependencies: + '@vitest/runner': link:../runner local-pkg: 0.4.2 mlly: 1.1.0 modern-node-polyfills: 0.0.9 @@ -727,6 +729,7 @@ importers: '@unocss/reset': ^0.48.3 '@vitejs/plugin-vue': ^4.0.0 '@vitejs/plugin-vue-jsx': ^3.0.0 + '@vitest/runner': workspace:^0.27.2 '@vitest/ws-client': workspace:* '@vueuse/core': ^9.10.0 ansi-to-html: ^0.7.2 @@ -764,6 +767,7 @@ importers: '@unocss/reset': 0.48.3 '@vitejs/plugin-vue': 4.0.0_vite@4.0.0+vue@3.2.45 '@vitejs/plugin-vue-jsx': 3.0.0_vite@4.0.0+vue@3.2.45 + '@vitest/runner': link:../runner '@vitest/ws-client': link:../ws-client '@vueuse/core': 9.10.0_vue@3.2.45 ansi-to-html: 0.7.2 diff --git a/test/core/test/diff.test.ts b/test/core/test/diff.test.ts index d57fcd938e61..04f8e0df4cff 100644 --- a/test/core/test/diff.test.ts +++ b/test/core/test/diff.test.ts @@ -6,7 +6,7 @@ test('displays an error for large objects', () => { const objectA = new Array(1000).fill(0).map((_, i) => ({ i, long: 'a'.repeat(i) })) const objectB = new Array(1000).fill(0).map((_, i) => ({ i, long: 'b'.repeat(i) })) const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify(objectA), stringify(objectB), console as any, { noColor: true }) + displayDiff(stringify(objectA), stringify(objectB), console as any) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` "Could not display diff. It's possible objects are too large to compare. Try increasing --outputDiffMaxSize option. @@ -16,7 +16,7 @@ test('displays an error for large objects', () => { test('displays an error for large objects', () => { const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify('undefined'), stringify('undefined'), console as any, { noColor: true }) + displayDiff(stringify('undefined'), stringify('undefined'), console as any) expect(console.error).not.toHaveBeenCalled() }) @@ -24,7 +24,7 @@ test('displays diff', () => { const objectA = { a: 1, b: 2 } const objectB = { a: 1, b: 3 } const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify(objectA), stringify(objectB), console as any, { noColor: true }) + displayDiff(stringify(objectA), stringify(objectB), console as any) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` " - Expected - 1 + Received + 1 @@ -42,7 +42,7 @@ test('displays long diff', () => { const objectA = { a: 1, b: 2, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 } const objectB = { a: 1, b: 3, k: 11, l: 12, m: 13, n: 14, p: 16, o: 17, r: 18, s: 23, t: 88, u: 21, v: 44, w: 23, x: 24, y: 25, z: 26 } const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify(objectA), stringify(objectB), console as any, { noColor: true, outputDiffMaxLines: 5 }) + displayDiff(stringify(objectA), stringify(objectB), console as any, { outputDiffMaxLines: 5 }) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` " - Expected - 5 + Received + 13 @@ -65,7 +65,7 @@ Morbi consectetur arcu nec lorem lacinia tempus.` Quisque pellentesque enim a elit faucibus cursus. Sed in tellus aliquet mauris interdum semper a in lacus.` const console = { log: vi.fn(), error: vi.fn() } - displayDiff((stringA), (objectB), console as any, { noColor: true, outputTruncateLength: 14 }) + displayDiff((stringA), (objectB), console as any, { outputTruncateLength: 14 }) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` " - Expected - 3 + Received + 3 diff --git a/tsconfig.json b/tsconfig.json index 2fd63dcc7452..ec9831421c10 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,10 +17,11 @@ "@vitest/ws-client": ["./packages/ws-client/src/index.ts"], "@vitest/ui": ["./packages/ui/node/index.ts"], "@vitest/utils": ["./packages/utils/src/index.ts"], + "@vitest/utils/*": ["./packages/utils/src/*"], "@vitest/spy": ["./packages/spy/src/index.ts"], "@vitest/expect": ["./packages/expect/src/index.ts"], "@vitest/runner": ["./packages/runner/src/index.ts"], - "@vitest/runner/utils": ["./packages/runner/src/utils/index.ts"], + "@vitest/runner/*": ["./packages/runner/src/*"], "@vitest/browser": ["./packages/browser/src/node/index.ts"], "#types": ["./packages/vitest/src/index.ts"], "~/*": ["./packages/ui/client/*"], From 2ab8cba1977264f596a7aa004aa6ad11e10f3b29 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 17:46:29 +0100 Subject: [PATCH 12/47] chore: lockfile --- pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b20eab8897cf..9d9a59d8ff23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -626,7 +626,7 @@ importers: packages/browser: specifiers: '@types/ws': ^8.5.4 - '@vitest/runner': workspace:^0.27.2 + '@vitest/runner': workspace:* '@vitest/ws-client': workspace:* local-pkg: ^0.4.2 mlly: ^1.1.0 @@ -729,7 +729,7 @@ importers: '@unocss/reset': ^0.48.3 '@vitejs/plugin-vue': ^4.0.0 '@vitejs/plugin-vue-jsx': ^3.0.0 - '@vitest/runner': workspace:^0.27.2 + '@vitest/runner': workspace:* '@vitest/ws-client': workspace:* '@vueuse/core': ^9.10.0 ansi-to-html: ^0.7.2 @@ -981,7 +981,7 @@ importers: packages/ws-client: specifiers: - '@vitest/runner': workspace:^0.27.2 + '@vitest/runner': workspace:* birpc: ^0.2.3 flatted: ^3.2.7 rollup: ^2.79.1 From 066ad8aa557a6f6d2f437a0111ab503738506e99 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 18:01:28 +0100 Subject: [PATCH 13/47] chore: cleanup --- packages/browser/src/client/runner.ts | 8 +++++++- packages/vitest/src/api/setup.ts | 4 ++-- packages/vitest/src/node/core.ts | 11 ++++++----- packages/vitest/src/node/state.ts | 16 +--------------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts index b74ea8852c19..a84aa70a09df 100644 --- a/packages/browser/src/client/runner.ts +++ b/packages/browser/src/client/runner.ts @@ -1,4 +1,4 @@ -import type { VitestRunner } from '@vitest/runner' +import type { TaskResult, VitestRunner } from '@vitest/runner' import type { VitestClient } from '@vitest/ws-client' import type { ResolvedConfig } from '#types' @@ -11,10 +11,16 @@ interface BrowserRunnerOptions { export class BrowserTestRunner implements VitestRunner { public config: ResolvedConfig hasMap = new Map() + client: VitestClient constructor(options: BrowserRunnerOptions) { this.config = options.config this.hasMap = options.browserHashMap + this.client = options.client + } + + onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { + return this.client.rpc.onTaskUpdate(task) } async importFile(filepath: string) { diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts index aed3de53b9ed..2457d76df8f0 100644 --- a/packages/vitest/src/api/setup.ts +++ b/packages/vitest/src/api/setup.ts @@ -51,8 +51,8 @@ export function setup(ctx: Vitest) { getFiles() { return ctx.state.getFiles() }, - async getPaths() { - return await ctx.state.getPaths() + getPaths() { + return ctx.state.getPaths() }, readFile(id) { return fs.readFile(id, 'utf-8') diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 2b62b29a4d99..ab5c95156741 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -313,15 +313,17 @@ export class Vitest { } async runFiles(paths: string[]) { - // TODO: support browser + paths = Array.from(new Set(paths)) + + this.state.collectPaths(paths) + + await this.report('onPathsCollected', paths) + if (this.config.browser) return - paths = Array.from(new Set(paths)) - // previous run await this.runningPromise - this.state.startCollectingPaths() // schedule the new run this.runningPromise = (async () => { @@ -348,7 +350,6 @@ export class Vitest { await this.cache.results.writeToCache() })() .finally(async () => { - this.state.finishCollectingPaths() if (!this.config.browser) await this.report('onFinished', this.state.getFiles(paths), this.state.getUnhandledErrors()) this.runningPromise = undefined diff --git a/packages/vitest/src/node/state.ts b/packages/vitest/src/node/state.ts index 54dc6ea31d40..44315fb52b8b 100644 --- a/packages/vitest/src/node/state.ts +++ b/packages/vitest/src/node/state.ts @@ -39,21 +39,7 @@ export class StateManager { return Array.from(this.errorsSet.values()) } - startCollectingPaths() { - let _resolve: CollectingPromise['resolve'] - const promise = new Promise((resolve) => { - _resolve = resolve - }) - this.collectingPromise = { promise, resolve: _resolve! } - } - - finishCollectingPaths() { - this.collectingPromise?.resolve() - this.collectingPromise = undefined - } - - async getPaths() { - await this.collectingPromise?.promise + getPaths() { return Array.from(this.pathsSet) } From 131d8fe2c72d2490fbfba6869d8ae31aab1f372a Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 18:14:29 +0100 Subject: [PATCH 14/47] chore: types --- packages/vitest/src/api/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vitest/src/api/types.ts b/packages/vitest/src/api/types.ts index 4e7aa64b6d9c..9024e3a92f3e 100644 --- a/packages/vitest/src/api/types.ts +++ b/packages/vitest/src/api/types.ts @@ -11,7 +11,7 @@ export interface WebSocketHandlers { onCollected(files?: File[]): Promise onTaskUpdate(packs: TaskResultPack[]): void getFiles(): File[] - getPaths(): Promise + getPaths(): string[] getConfig(): ResolvedConfig getModuleGraph(id: string): Promise getTransformResult(id: string): Promise From c6339b2e01f088c087575fa6f933e65c0ea57c0d Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 18:49:12 +0100 Subject: [PATCH 15/47] chore: change c8 magic number --- packages/coverage-c8/src/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index 7de1b28af7a6..1218a8570698 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -129,7 +129,7 @@ export class C8CoverageProvider implements CoverageProvider { // This is a magic number. It corresponds to the amount of code // that we add in packages/vite-node/src/client.ts:114 (vm.runInThisContext) // TODO: Include our transformations in sourcemaps - const offset = 203 + const offset = 186 report._getSourceMap = (coverage: Profiler.ScriptCoverage) => { const path = _url.pathToFileURL(coverage.url.split('?')[0]).href From f01113ef5e9c314d43d6728b77356bd28471286f Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 18:56:37 +0100 Subject: [PATCH 16/47] fix: offset --- packages/coverage-c8/src/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index 1218a8570698..5e5d2b5b8580 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -129,7 +129,7 @@ export class C8CoverageProvider implements CoverageProvider { // This is a magic number. It corresponds to the amount of code // that we add in packages/vite-node/src/client.ts:114 (vm.runInThisContext) // TODO: Include our transformations in sourcemaps - const offset = 186 + const offset = 185 report._getSourceMap = (coverage: Profiler.ScriptCoverage) => { const path = _url.pathToFileURL(coverage.url.split('?')[0]).href From 629cbc143d65b3bf8838832550edf49c8f44de0b Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 19:07:15 +0100 Subject: [PATCH 17/47] chore: cleanup --- packages/vitest/src/node/reporters/base.ts | 8 +-- packages/vitest/src/node/reporters/verbose.ts | 2 +- .../src/node/sequencers/RandomSequencer.ts | 2 +- packages/vitest/src/utils/base.ts | 65 ------------------- packages/vitest/src/utils/tasks.ts | 4 +- test/core/test/utils.spec.ts | 3 +- 6 files changed, 9 insertions(+), 75 deletions(-) diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index fcfa6fab7190..5a5fd897655f 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -84,7 +84,7 @@ export abstract class BaseReporter implements Reporter { // print short errors, full errors will be at the end in summary for (const test of failed) { - logger.log(c.red(` ${pointer} ${getFullName(test)}`)) + logger.log(c.red(` ${pointer} ${getFullName(test, c.dim(' > '))}`)) test.result?.errors?.forEach((e) => { logger.log(c.red(` ${F_RIGHT} ${(e as any)?.message}`)) }) @@ -174,7 +174,7 @@ export abstract class BaseReporter implements Reporter { if (!this.shouldLog(log)) return const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : undefined - this.ctx.logger.log(c.gray(log.type + c.dim(` | ${task ? getFullName(task) : 'unknown test'}`))) + this.ctx.logger.log(c.gray(log.type + c.dim(` | ${task ? getFullName(task, c.dim(' > ')) : 'unknown test'}`))) process[log.type].write(`${log.content}\n`) } @@ -295,7 +295,7 @@ export abstract class BaseReporter implements Reporter { const group = bench.suite if (!group) continue - const groupName = getFullName(group) + const groupName = getFullName(group, c.dim(' > ')) logger.log(` ${bench.name}${c.dim(` - ${groupName}`)}`) const siblings = group.tasks .filter(i => i.result?.benchmark && i !== bench) @@ -324,7 +324,7 @@ export abstract class BaseReporter implements Reporter { for (const task of tasks) { const filepath = (task as File)?.filepath || '' const projectName = (task as File)?.projectName || task.file?.projectName - let name = getFullName(task) + let name = getFullName(task, c.dim(' > ')) if (filepath) name = `${name} ${c.dim(`[ ${this.relative(filepath)} ]`)}` diff --git a/packages/vitest/src/node/reporters/verbose.ts b/packages/vitest/src/node/reporters/verbose.ts index 4fbd637ea7c4..9791cbc0175b 100644 --- a/packages/vitest/src/node/reporters/verbose.ts +++ b/packages/vitest/src/node/reporters/verbose.ts @@ -17,7 +17,7 @@ export class VerboseReporter extends DefaultReporter { for (const pack of packs) { const task = this.ctx.state.idMap.get(pack[0]) if (task && task.type === 'test' && task.result?.state && task.result?.state !== 'run') { - let title = ` ${getStateSymbol(task)} ${getFullName(task)}` + let title = ` ${getStateSymbol(task)} ${getFullName(task, c.dim(' > '))}` if (this.ctx.config.logHeapUsage && task.result.heap != null) title += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`) this.ctx.logger.log(title) diff --git a/packages/vitest/src/node/sequencers/RandomSequencer.ts b/packages/vitest/src/node/sequencers/RandomSequencer.ts index c92c82f62080..d6edc463813e 100644 --- a/packages/vitest/src/node/sequencers/RandomSequencer.ts +++ b/packages/vitest/src/node/sequencers/RandomSequencer.ts @@ -1,4 +1,4 @@ -import { shuffle } from '../../utils' +import { shuffle } from '@vitest/utils' import { BaseSequencer } from './BaseSequencer' export class RandomSequencer extends BaseSequencer { diff --git a/packages/vitest/src/utils/base.ts b/packages/vitest/src/utils/base.ts index dd4faa6838c3..1761ffd739cf 100644 --- a/packages/vitest/src/utils/base.ts +++ b/packages/vitest/src/utils/base.ts @@ -1,4 +1,3 @@ -import { RealDate } from '../integrations/mock/date' import type { Arrayable, DeepMerge, Nullable } from '../types' function isFinalObj(obj: any) { @@ -49,43 +48,6 @@ export function getType(value: unknown): string { return Object.prototype.toString.apply(value).slice(8, -1) } -function getOwnProperties(obj: any) { - const ownProps = new Set() - if (isFinalObj(obj)) - return [] - collectOwnProperties(obj, ownProps) - return Array.from(ownProps) -} - -export function deepClone(val: T): T { - const seen = new WeakMap() - return clone(val, seen) -} - -export function clone(val: T, seen: WeakMap): T { - let k: any, out: any - if (seen.has(val)) - return seen.get(val) - if (Array.isArray(val)) { - out = Array(k = val.length) - seen.set(val, out) - while (k--) - out[k] = clone(val[k], seen) - return out as any - } - - if (Object.prototype.toString.call(val) === '[object Object]') { - out = Object.create(Object.getPrototypeOf(val)) - seen.set(val, out) - // we don't need properties from prototype - const props = getOwnProperties(val) - for (const k of props) - out[k] = clone((val as any)[k], seen) - return out - } - - return val -} /** * Convert `Arrayable` to `Array` * @@ -147,35 +109,8 @@ function isMergeableObject(item: any): item is Object { return isPlainObject(item) && !Array.isArray(item) } -export function assertTypes(value: unknown, name: string, types: string[]): void { - const receivedType = typeof value - const pass = types.includes(receivedType) - if (!pass) - throw new TypeError(`${name} value must be ${types.join(' or ')}, received "${receivedType}"`) -} - export function stdout(): NodeJS.WriteStream { // @ts-expect-error Node.js maps process.stdout to console._stdout // eslint-disable-next-line no-console return console._stdout || process.stdout } - -function random(seed: number) { - const x = Math.sin(seed++) * 10000 - return x - Math.floor(x) -} - -export function shuffle(array: T[], seed = RealDate.now()): T[] { - let length = array.length - - while (length) { - const index = Math.floor(random(seed) * length--) - - const previous = array[length] - array[length] = array[index] - array[index] = previous - ++seed - } - - return array -} diff --git a/packages/vitest/src/utils/tasks.ts b/packages/vitest/src/utils/tasks.ts index c504b82834f6..30146713ee7c 100644 --- a/packages/vitest/src/utils/tasks.ts +++ b/packages/vitest/src/utils/tasks.ts @@ -2,9 +2,7 @@ import { getNames, getTests } from '@vitest/runner/utils' import type { Arrayable, Suite, Task } from '../types' import { toArray } from './base' -export { getNames } from '@vitest/runner/utils' - -export { getTasks, getTests, getSuites, hasTests, hasFailed } from '@vitest/runner/utils' +export { getTasks, getTests, getSuites, hasTests, hasFailed, getNames } from '@vitest/runner/utils' export function hasBenchmark(suite: Arrayable): boolean { return toArray(suite).some(s => s?.tasks?.some(c => c.meta?.benchmark || hasBenchmark(c as Suite))) diff --git a/test/core/test/utils.spec.ts b/test/core/test/utils.spec.ts index 7ebfda54a73d..3f9fa65eb49c 100644 --- a/test/core/test/utils.spec.ts +++ b/test/core/test/utils.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'vitest' -import { assertTypes, deepClone, deepMerge, objectAttr, resetModules, toArray } from '../../../packages/vitest/src/utils' +import { assertTypes, deepClone, objectAttr, toArray } from '@vitest/utils' +import { deepMerge, resetModules } from '../../../packages/vitest/src/utils' import { deepMergeSnapshot } from '../../../packages/vitest/src/integrations/snapshot/port/utils' import type { ModuleCacheMap } from '../../../packages/vite-node/src/types' From 37147ed50ca04b0ae097e05d6d0c76aa0304dbc8 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 19:15:58 +0100 Subject: [PATCH 18/47] fix: generate inline snapshots --- packages/vitest/src/integrations/snapshot/client.ts | 1 + packages/vitest/src/runtime/runners/node.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/integrations/snapshot/client.ts b/packages/vitest/src/integrations/snapshot/client.ts index e1d8c616705c..5b13be0c80ba 100644 --- a/packages/vitest/src/integrations/snapshot/client.ts +++ b/packages/vitest/src/integrations/snapshot/client.ts @@ -25,6 +25,7 @@ interface AssertOptions { } export class SnapshotClient { + id = Date.now() test: Test | undefined snapshotState: SnapshotState | undefined snapshotStateMap = new Map() diff --git a/packages/vitest/src/runtime/runners/node.ts b/packages/vitest/src/runtime/runners/node.ts index 553ec90df1a2..e256d65b0a6a 100644 --- a/packages/vitest/src/runtime/runners/node.ts +++ b/packages/vitest/src/runtime/runners/node.ts @@ -30,7 +30,7 @@ export class NodeTestRunner implements VitestRunner { async onAfterRun() { const coverage = await takeCoverageInsideWorker(this.config.coverage) rpc().onAfterSuiteRun({ coverage }) - this.snapshotClient.saveCurrent() + await this.snapshotClient.saveCurrent() } onAfterRunSuite(suite: Suite) { From af1d36a7f05e854687e116af6ac72d6357c63aaf Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 19:45:01 +0100 Subject: [PATCH 19/47] chore: cleanup --- packages/expect/src/jest-expect.ts | 7 ++--- packages/expect/src/jest-matcher-utils.ts | 2 +- packages/expect/src/types.ts | 1 + packages/vitest/src/node/error.ts | 6 +++- .../test/__snapshots__/mocked.test.ts.snap | 28 +++++++++---------- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index 28e70036cd60..cb7372c66f30 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1,14 +1,13 @@ import c from 'picocolors' import { AssertionError } from 'chai' import { assertTypes } from '@vitest/utils' -import { unifiedDiff } from '@vitest/utils/diff' import type { Constructable } from '@vitest/utils' import type { EnhancedSpy } from '@vitest/spy' import { isMockFunction } from '@vitest/spy' import type { ChaiPlugin } from './types' import { arrayBufferEquality, generateToBeMessage, iterableEquality, equals as jestEquals, sparseArrayEquality, subsetEquality, typeEquality } from './jest-utils' import type { AsymmetricMatcher } from './jest-asymmetric-matchers' -import { stringify } from './jest-matcher-utils' +import { diff, stringify } from './jest-matcher-utils' import { JEST_MATCHERS_OBJECT } from './constants' // Jest Expect Compact @@ -355,7 +354,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { msg += c.gray(`\n\nReceived: \n${spy.mock.calls.map((callArg, i) => { let methodCall = c.bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call:\n\n`) if (actualCall) - methodCall += unifiedDiff(stringify(callArg), stringify(actualCall), { showLegend: false }) + methodCall += diff(callArg, actualCall, { showLegend: false }) else methodCall += stringify(callArg).split('\n').map(line => ` ${line}`).join('\n') @@ -369,7 +368,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { msg += c.gray(`\n\nReceived: \n${spy.mock.results.map((callReturn, i) => { let methodCall = c.bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call return:\n\n`) if (actualReturn) - methodCall += unifiedDiff(stringify(callReturn.value), stringify(actualReturn), { showLegend: false }) + methodCall += diff(callReturn.value, actualReturn, { showLegend: false }) else methodCall += stringify(callReturn).split('\n').map(line => ` ${line}`).join('\n') diff --git a/packages/expect/src/jest-matcher-utils.ts b/packages/expect/src/jest-matcher-utils.ts index d64cffb1beec..ec9bfe7bd068 100644 --- a/packages/expect/src/jest-matcher-utils.ts +++ b/packages/expect/src/jest-matcher-utils.ts @@ -88,11 +88,11 @@ export const printExpected = (value: unknown): string => EXPECTED_COLOR(replaceTrailingSpaces(stringify(value))) // TODO: do something with options -// eslint-disable-next-line @typescript-eslint/no-unused-vars export function diff(a: any, b: any, options?: DiffOptions) { return unifiedDiff(stringify(b), stringify(a), { colorDim: c.dim, colorSuccess: c.green, colorError: c.red, + showLegend: options?.showLegend, }) } diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 3ff834b17753..01a3a85a8b00 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -47,6 +47,7 @@ export interface DiffOptions { patchColor?: Formatter // pretty-format type compareKeys?: any + showLegend?: boolean } export interface MatcherState { diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 2514f301c048..0225e1072f74 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -90,6 +90,9 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro outputTruncateLength: ctx.config.outputTruncateLength, outputDiffLines: ctx.config.outputDiffLines, outputDiffMaxLines: ctx.config.outputDiffMaxLines, + colorDim: c.dim, + colorError: c.red, + colorSuccess: c.green, }) } } @@ -165,7 +168,8 @@ function handleImportOutsideModuleError(stack: string, ctx: Vitest) { export function displayDiff(actual: string, expected: string, console: Console, options: Omit = {}) { const diff = unifiedDiff(actual, expected, options) - const dim = options.colorDim || c.dim + globalThis.console.log(options) + const dim = options.colorDim || ((str: string) => str) const black = options.colorDim ? c.black : (str: string) => str if (diff) console.error(diff + '\n') diff --git a/test/core/test/__snapshots__/mocked.test.ts.snap b/test/core/test/__snapshots__/mocked.test.ts.snap index 858f49ee0236..e896ff91c76d 100644 --- a/test/core/test/__snapshots__/mocked.test.ts.snap +++ b/test/core/test/__snapshots__/mocked.test.ts.snap @@ -6,8 +6,8 @@ exports[`mocked function which fails on toReturnWith > just one call 1`] = ` Received:  1st spy call return: - 2 - 1 + 1 + 2  Number of calls: 1 @@ -20,18 +20,18 @@ exports[`mocked function which fails on toReturnWith > multi calls 1`] = ` Received:  1st spy call return: - 2 - 1 + 1 + 2  2nd spy call return: - 2 - 1 + 1 + 2  3rd spy call return: - 2 - 1 + 1 + 2  Number of calls: 3 @@ -45,22 +45,22 @@ Received:  1st spy call return:  Object { - - \\"a\\": \\"4\\", - + \\"a\\": \\"1\\", + - \\"a\\": \\"1\\", + + \\"a\\": \\"4\\", }  2nd spy call return:  Object { - - \\"a\\": \\"4\\", - + \\"a\\": \\"1\\", + - \\"a\\": \\"1\\", + + \\"a\\": \\"4\\", }  3rd spy call return:  Object { - - \\"a\\": \\"4\\", - + \\"a\\": \\"1\\", + - \\"a\\": \\"1\\", + + \\"a\\": \\"4\\", }  From 131062299f3b65869cbbd4e59d98f1d89d9cf161 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 19:51:04 +0100 Subject: [PATCH 20/47] chore: cleanup --- packages/vitest/src/node/error.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 0225e1072f74..362b9864ad43 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -168,7 +168,6 @@ function handleImportOutsideModuleError(stack: string, ctx: Vitest) { export function displayDiff(actual: string, expected: string, console: Console, options: Omit = {}) { const diff = unifiedDiff(actual, expected, options) - globalThis.console.log(options) const dim = options.colorDim || ((str: string) => str) const black = options.colorDim ? c.black : (str: string) => str if (diff) From 6d59bd07bb544123889f91f687e40aa6cbb27d8f Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 19:51:11 +0100 Subject: [PATCH 21/47] chore: cleanup --- packages/ui/client/components/views/ViewReport.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/client/components/views/ViewReport.vue b/packages/ui/client/components/views/ViewReport.vue index ebd8a97cc470..51188bcba22e 100644 --- a/packages/ui/client/components/views/ViewReport.vue +++ b/packages/ui/client/components/views/ViewReport.vue @@ -18,7 +18,7 @@ function collectFailed(task: Task, level: number): LeveledTask[] { if (task.result?.state !== 'fail') return [] - if (task.type === 'test' || task.type === 'benchmark' || task.type === 'typecheck') + if (task.type === 'test' || task.type === 'custom') return [{ ...task, level }] else return [{ ...task, level }, ...task.tasks.flatMap(t => collectFailed(t, level + 1))] From 7e2c9acf7d15b66cddafb77c41aa2801ec9258fa Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 20:00:37 +0100 Subject: [PATCH 22/47] chore: cleanup --- test/reporters/tests/__snapshots__/html.test.ts.snap | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/reporters/tests/__snapshots__/html.test.ts.snap b/test/reporters/tests/__snapshots__/html.test.ts.snap index e5772658ef16..49db8c36242b 100644 --- a/test/reporters/tests/__snapshots__/html.test.ts.snap +++ b/test/reporters/tests/__snapshots__/html.test.ts.snap @@ -103,7 +103,9 @@ exports[`html reporter > resolves to "failing" status for test file "json-fail" ], }, }, - "paths": [], + "paths": [ + "/test/reporters/fixtures/json-fail.test.ts", + ], } `; @@ -213,6 +215,8 @@ exports[`html reporter > resolves to "passing" status for test file "all-passing ], }, }, - "paths": [], + "paths": [ + "/test/reporters/fixtures/all-passing-or-skipped.test.ts", + ], } `; From 9d75c5c41b265e0c17838a8bcea5c7da6bdc8746 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 20:13:03 +0100 Subject: [PATCH 23/47] chore: fix config reset --- packages/vitest/src/integrations/vi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 1761494c6488..d67a481b8c0e 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -340,7 +340,7 @@ class VitestUtils { public resetConfig() { if (this._config) { const state = getWorkerState() - state.config = { ...this._config } + Object.assign(state.config, this._config) } } } From 7fa6a6f18dde8da32c4ab5a65f3e1da3d68d2bb0 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 20:37:14 +0100 Subject: [PATCH 24/47] chore: cleanup --- .eslintignore | 2 + package.json | 2 +- .../client/components/views/ViewReport.cy.tsx | 3 + packages/ui/cypress.config.ts | 3 + packages/ui/package.json | 3 +- packages/utils/package.json | 3 +- packages/vitest/package.json | 2 +- pnpm-lock.yaml | 55 ++++++++++++++++--- 8 files changed, 61 insertions(+), 12 deletions(-) diff --git a/.eslintignore b/.eslintignore index c21fcfc1e06b..095125aecd40 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,6 @@ dist +html +bench.json node_modules *.svelte *.snap diff --git a/package.json b/package.json index 2128323ad7a2..566b9c4a374f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "node-fetch-native": "^1.0.1", "npm-run-all": "^4.1.5", "ohmyfetch": "^0.4.21", - "pathe": "^0.2.0", + "pathe": "^1.0.0", "pnpm": "7.23.0", "rimraf": "^3.0.2", "rollup": "^2.79.1", diff --git a/packages/ui/client/components/views/ViewReport.cy.tsx b/packages/ui/client/components/views/ViewReport.cy.tsx index 25c967c5c2af..acefd83b7401 100644 --- a/packages/ui/client/components/views/ViewReport.cy.tsx +++ b/packages/ui/client/components/views/ViewReport.cy.tsx @@ -1,6 +1,9 @@ import { faker } from '@faker-js/faker' import ViewReport from './ViewReport.vue' import type { File } from '#types' +import { config } from '~/composables/client' + +config.value.root = '' const taskErrorSelector = '.task-error' const viewReportSelector = '[data-testid=view-report]' diff --git a/packages/ui/cypress.config.ts b/packages/ui/cypress.config.ts index 27c6557c5b10..1d543f6ffc8f 100644 --- a/packages/ui/cypress.config.ts +++ b/packages/ui/cypress.config.ts @@ -9,6 +9,9 @@ export default defineConfig({ framework: 'vue', bundler: 'vite', viteConfig: { + define: { + 'process.env.NODE_DEBUG': '"false"', + }, configFile: resolve('./cypress/vite.config.ts'), }, }, diff --git a/packages/ui/package.json b/packages/ui/package.json index df57fce1b73e..2b1e8d1cea49 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -41,6 +41,8 @@ "dependencies": { "fast-glob": "^3.2.12", "flatted": "^3.2.7", + "pathe": "^1.0.0", + "picocolors": "^1.0.0", "sirv": "^2.0.2" }, "devDependencies": { @@ -64,7 +66,6 @@ "d3-graph-controller": "^2.5.1", "diff": "^5.1.0", "floating-vue": "^2.0.0-y.0", - "picocolors": "^1.0.0", "rollup": "^2.79.1", "splitpanes": "^3.1.5", "unocss": "^0.48.3", diff --git a/packages/utils/package.json b/packages/utils/package.json index 0a5ae89a6669..fc89091fb54c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -41,8 +41,7 @@ "diff": "^5.1.0", "loupe": "^2.3.6", "picocolors": "^1.0.0", - "pretty-format": "^27.5.1", - "util": "^0.12.5" + "pretty-format": "^27.5.1" }, "devDependencies": { "@types/diff": "^5.0.2" diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 902e2b310674..f30312978fe0 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -116,6 +116,7 @@ "chai": "^4.3.7", "debug": "^4.3.4", "local-pkg": "^0.4.2", + "pathe": "^1.0.0", "picocolors": "^1.0.0", "source-map": "^0.6.1", "std-env": "^3.3.1", @@ -156,7 +157,6 @@ "mlly": "^1.1.0", "natural-compare": "^1.4.0", "p-limit": "^4.0.0", - "pathe": "^0.2.0", "pkg-types": "^1.0.1", "pretty-format": "^27.5.1", "prompts": "^2.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d9a59d8ff23..9f32739606d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,7 +36,7 @@ importers: node-fetch-native: ^1.0.1 npm-run-all: ^4.1.5 ohmyfetch: ^0.4.21 - pathe: ^0.2.0 + pathe: ^1.0.0 pnpm: 7.23.0 rimraf: ^3.0.2 rollup: ^2.79.1 @@ -76,7 +76,7 @@ importers: node-fetch-native: 1.0.1 npm-run-all: 4.1.5 ohmyfetch: 0.4.21 - pathe: 0.2.0 + pathe: 1.0.0 pnpm: 7.23.0 rimraf: 3.0.2 rollup: 2.79.1 @@ -742,6 +742,7 @@ importers: fast-glob: ^3.2.12 flatted: ^3.2.7 floating-vue: ^2.0.0-y.0 + pathe: ^1.0.0 picocolors: ^1.0.0 rollup: ^2.79.1 sirv: ^2.0.2 @@ -756,6 +757,8 @@ importers: dependencies: fast-glob: 3.2.12 flatted: 3.2.7 + pathe: 1.0.0 + picocolors: 1.0.0 sirv: 2.0.2 devDependencies: '@faker-js/faker': 7.6.0 @@ -778,7 +781,6 @@ importers: d3-graph-controller: 2.5.1 diff: 5.1.0 floating-vue: 2.0.0-y.0_vue@3.2.45 - picocolors: 1.0.0 rollup: 2.79.1 splitpanes: 3.1.5 unocss: 0.48.3_rollup@2.79.1+vite@4.0.0 @@ -797,14 +799,12 @@ importers: loupe: ^2.3.6 picocolors: ^1.0.0 pretty-format: ^27.5.1 - util: ^0.12.5 dependencies: cli-truncate: 3.1.0 diff: 5.1.0 loupe: 2.3.6 picocolors: 1.0.0 pretty-format: 27.5.1 - util: 0.12.5 devDependencies: '@types/diff': 5.0.2 @@ -881,7 +881,7 @@ importers: mlly: ^1.1.0 natural-compare: ^1.4.0 p-limit: ^4.0.0 - pathe: ^0.2.0 + pathe: ^1.0.0 picocolors: ^1.0.0 pkg-types: ^1.0.1 pretty-format: ^27.5.1 @@ -914,6 +914,7 @@ importers: chai: 4.3.7 debug: 4.3.4 local-pkg: 0.4.2 + pathe: 1.0.0 picocolors: 1.0.0 source-map: 0.6.1 std-env: 3.3.1 @@ -953,7 +954,6 @@ importers: mlly: 1.1.0 natural-compare: 1.4.0 p-limit: 4.0.0 - pathe: 0.2.0 pkg-types: 1.0.1 pretty-format: 27.5.1 prompts: 2.4.2 @@ -9527,6 +9527,7 @@ packages: /available-typed-arrays/1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} + dev: true /avvio/8.2.0: resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==} @@ -10186,6 +10187,7 @@ packages: dependencies: function-bind: 1.1.1 get-intrinsic: 1.1.3 + dev: true /call-me-maybe/1.0.1: resolution: {integrity: sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==} @@ -11405,6 +11407,7 @@ packages: dependencies: has-property-descriptors: 1.0.0 object-keys: 1.1.1 + dev: true /define-property/0.2.5: resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} @@ -11873,6 +11876,7 @@ packages: string.prototype.trimend: 1.0.5 string.prototype.trimstart: 1.0.5 unbox-primitive: 1.0.2 + dev: true /es-array-method-boxes-properly/1.0.0: resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} @@ -11912,6 +11916,7 @@ packages: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 + dev: true /es5-shim/4.6.7: resolution: {integrity: sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==} @@ -13047,6 +13052,7 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 + dev: true /for-in/1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} @@ -13258,6 +13264,7 @@ packages: define-properties: 1.1.4 es-abstract: 1.20.4 functions-have-names: 1.2.3 + dev: true /functional-red-black-tree/1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} @@ -13265,6 +13272,7 @@ packages: /functions-have-names/1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true /gauge/3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} @@ -13299,6 +13307,7 @@ packages: function-bind: 1.1.1 has: 1.0.3 has-symbols: 1.0.3 + dev: true /get-own-enumerable-property-symbols/3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} @@ -13345,6 +13354,7 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.1.3 + dev: true /get-tsconfig/4.3.0: resolution: {integrity: sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ==} @@ -13609,6 +13619,7 @@ packages: /has-bigints/1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true /has-flag/3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -13629,16 +13640,19 @@ packages: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.1.3 + dev: true /has-symbols/1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + dev: true /has-tostringtag/1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 + dev: true /has-unicode/2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} @@ -14115,6 +14129,7 @@ packages: get-intrinsic: 1.1.3 has: 1.0.3 side-channel: 1.0.4 + dev: true /internmap/2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} @@ -14171,6 +14186,7 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 + dev: true /is-arrayish/0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -14179,6 +14195,7 @@ packages: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.2 + dev: true /is-binary-path/1.0.1: resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} @@ -14201,6 +14218,7 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 + dev: true /is-buffer/1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -14226,6 +14244,7 @@ packages: /is-callable/1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} + dev: true /is-ci/2.0.0: resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} @@ -14265,6 +14284,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-decimal/1.0.4: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} @@ -14340,6 +14360,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-glob/3.1.0: resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} @@ -14382,6 +14403,7 @@ packages: /is-negative-zero/2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} + dev: true /is-node-process/1.0.1: resolution: {integrity: sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==} @@ -14392,6 +14414,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-number/3.0.0: resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} @@ -14451,6 +14474,7 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 + dev: true /is-regexp/1.0.0: resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} @@ -14465,6 +14489,7 @@ packages: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: call-bind: 1.0.2 + dev: true /is-stream/1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} @@ -14486,6 +14511,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-subset/0.1.1: resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} @@ -14496,6 +14522,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 + dev: true /is-typed-array/1.1.9: resolution: {integrity: sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==} @@ -14506,6 +14533,7 @@ packages: es-abstract: 1.20.4 for-each: 0.3.3 has-tostringtag: 1.0.0 + dev: true /is-typedarray/1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -14529,6 +14557,7 @@ packages: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 + dev: true /is-weakset/2.0.2: resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} @@ -16398,6 +16427,7 @@ packages: /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + dev: true /object-is/1.1.5: resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} @@ -16410,6 +16440,7 @@ packages: /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + dev: true /object-visit/1.0.1: resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} @@ -16426,6 +16457,7 @@ packages: define-properties: 1.1.4 has-symbols: 1.0.3 object-keys: 1.1.1 + dev: true /object.entries/1.1.5: resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==} @@ -18078,6 +18110,7 @@ packages: call-bind: 1.0.2 define-properties: 1.1.4 functions-have-names: 1.2.3 + dev: true /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} @@ -18464,6 +18497,7 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.1.3 is-regex: 1.1.4 + dev: true /safe-regex/1.1.0: resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} @@ -18759,6 +18793,7 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.1.3 object-inspect: 1.12.2 + dev: true /siginfo/2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -19230,6 +19265,7 @@ packages: call-bind: 1.0.2 define-properties: 1.1.4 es-abstract: 1.20.4 + dev: true /string.prototype.trimstart/1.0.5: resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} @@ -19237,6 +19273,7 @@ packages: call-bind: 1.0.2 define-properties: 1.1.4 es-abstract: 1.20.4 + dev: true /string_decoder/1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -20131,6 +20168,7 @@ packages: has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + dev: true /unbzip2-stream/1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} @@ -20680,6 +20718,7 @@ packages: is-generator-function: 1.0.10 is-typed-array: 1.1.9 which-typed-array: 1.1.8 + dev: true /utila/0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -21386,6 +21425,7 @@ packages: is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 + dev: true /which-collection/1.0.1: resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} @@ -21406,6 +21446,7 @@ packages: for-each: 0.3.3 has-tostringtag: 1.0.0 is-typed-array: 1.1.9 + dev: true /which/1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} From 33985999d1d3529daa5bfab480b9bfda395cc4f3 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 20 Jan 2023 20:57:30 +0100 Subject: [PATCH 25/47] chore: cleanup --- packages/ui/cypress.config.ts | 3 +++ test/snapshots/tools/generate-inline-test.mjs | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/ui/cypress.config.ts b/packages/ui/cypress.config.ts index 1d543f6ffc8f..cf9541384a02 100644 --- a/packages/ui/cypress.config.ts +++ b/packages/ui/cypress.config.ts @@ -12,6 +12,9 @@ export default defineConfig({ define: { 'process.env.NODE_DEBUG': '"false"', }, + alias: { + '@vitest/runner/utils': resolve('../runner/src/utils.ts'), + }, configFile: resolve('./cypress/vite.config.ts'), }, }, diff --git a/test/snapshots/tools/generate-inline-test.mjs b/test/snapshots/tools/generate-inline-test.mjs index 133b448ca253..e72c75dccc63 100644 --- a/test/snapshots/tools/generate-inline-test.mjs +++ b/test/snapshots/tools/generate-inline-test.mjs @@ -1,8 +1,8 @@ import fs from 'fs/promises' import { fileURLToPath } from 'url' -import pathe from 'pathe' +import { dirname, resolve } from 'pathe' -const dirname = pathe.dirname(fileURLToPath(import.meta.url)) +const dir = dirname(fileURLToPath(import.meta.url)) export async function generateInlineTest(templatePath, testpath) { const template = await fs.readFile(templatePath, 'utf8') @@ -10,8 +10,8 @@ export async function generateInlineTest(templatePath, testpath) { console.log(`Generated ${testpath}`) } -const filepath = pathe.resolve(dirname, '../test-update/snapshots-inline-js.test.js') -const template = pathe.resolve(dirname, './inline-test-template.js'); +const filepath = resolve(dir, '../test-update/snapshots-inline-js.test.js') +const template = resolve(dir, './inline-test-template.js'); (async () => { await generateInlineTest(template, filepath) From 943ae34316b0d91fbce5cbb49da3f7a6f43dc446 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 09:08:07 +0100 Subject: [PATCH 26/47] chore: upgrade pathe --- .github/renovate.json5 | 2 -- .tazerc.json | 3 +- package.json | 2 +- packages/coverage-c8/package.json | 2 +- packages/coverage-istanbul/package.json | 2 +- packages/ui/package.json | 2 +- packages/vite-node/package.json | 2 +- packages/vitest/package.json | 2 +- pnpm-lock.yaml | 42 ++++++++++++------------- test/vite-config/package.json | 2 +- 10 files changed, 29 insertions(+), 32 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 861f0fee557f..a2b7fb46871e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -42,8 +42,6 @@ "@testing-library/user-event", // TODO: migrate "pretty-format", - // TODO: breaking changes - "pathe", // TODO: vite-plugin-pwa issue "esno", // user can install any version diff --git a/.tazerc.json b/.tazerc.json index 70f32d894238..d470b5832072 100644 --- a/.tazerc.json +++ b/.tazerc.json @@ -2,8 +2,7 @@ "exclude": [ "vue", "puppeteer", - "pretty-format", - "pathe" + "pretty-format" ], "packageMode": { "vue": "minor", diff --git a/package.json b/package.json index 566b9c4a374f..27676046b96d 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "node-fetch-native": "^1.0.1", "npm-run-all": "^4.1.5", "ohmyfetch": "^0.4.21", - "pathe": "^1.0.0", + "pathe": "^1.1.0", "pnpm": "7.23.0", "rimraf": "^3.0.2", "rollup": "^2.79.1", diff --git a/packages/coverage-c8/package.json b/packages/coverage-c8/package.json index 63bf16bd681b..7036f32b5b14 100644 --- a/packages/coverage-c8/package.json +++ b/packages/coverage-c8/package.json @@ -46,7 +46,7 @@ "vitest": "workspace:*" }, "devDependencies": { - "pathe": "^0.2.0", + "pathe": "^1.1.0", "vite-node": "workspace:*" } } diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index ccb12fffac0d..1671d8ce652e 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -56,6 +56,6 @@ "@types/istanbul-lib-report": "^3.0.0", "@types/istanbul-lib-source-maps": "^4.0.1", "@types/istanbul-reports": "^3.0.1", - "pathe": "^0.2.0" + "pathe": "^1.1.0" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 2b1e8d1cea49..f12f1026ca8b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -41,7 +41,7 @@ "dependencies": { "fast-glob": "^3.2.12", "flatted": "^3.2.7", - "pathe": "^1.0.0", + "pathe": "^1.1.0", "picocolors": "^1.0.0", "sirv": "^2.0.2" }, diff --git a/packages/vite-node/package.json b/packages/vite-node/package.json index 706152299cb4..17600af447dd 100644 --- a/packages/vite-node/package.json +++ b/packages/vite-node/package.json @@ -79,7 +79,7 @@ "cac": "^6.7.14", "debug": "^4.3.4", "mlly": "^1.1.0", - "pathe": "^0.2.0", + "pathe": "^1.1.0", "picocolors": "^1.0.0", "source-map": "^0.6.1", "source-map-support": "^0.5.21", diff --git a/packages/vitest/package.json b/packages/vitest/package.json index f30312978fe0..8d95b40511f8 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -116,7 +116,7 @@ "chai": "^4.3.7", "debug": "^4.3.4", "local-pkg": "^0.4.2", - "pathe": "^1.0.0", + "pathe": "^1.1.0", "picocolors": "^1.0.0", "source-map": "^0.6.1", "std-env": "^3.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f32739606d6..14a9bdb07690 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,7 +36,7 @@ importers: node-fetch-native: ^1.0.1 npm-run-all: ^4.1.5 ohmyfetch: ^0.4.21 - pathe: ^1.0.0 + pathe: ^1.1.0 pnpm: 7.23.0 rimraf: ^3.0.2 rollup: ^2.79.1 @@ -76,7 +76,7 @@ importers: node-fetch-native: 1.0.1 npm-run-all: 4.1.5 ohmyfetch: 0.4.21 - pathe: 1.0.0 + pathe: 1.1.0 pnpm: 7.23.0 rimraf: 3.0.2 rollup: 2.79.1 @@ -651,14 +651,14 @@ importers: packages/coverage-c8: specifiers: c8: ^7.12.0 - pathe: ^0.2.0 + pathe: ^1.1.0 vite-node: workspace:* vitest: workspace:* dependencies: c8: 7.12.0 vitest: link:../vitest devDependencies: - pathe: 0.2.0 + pathe: 1.1.0 vite-node: link:../vite-node packages/coverage-istanbul: @@ -673,7 +673,7 @@ importers: istanbul-lib-report: ^3.0.0 istanbul-lib-source-maps: ^4.0.1 istanbul-reports: ^3.1.5 - pathe: ^0.2.0 + pathe: ^1.1.0 test-exclude: ^6.0.0 vitest: workspace:* dependencies: @@ -690,7 +690,7 @@ importers: '@types/istanbul-lib-report': 3.0.0 '@types/istanbul-lib-source-maps': 4.0.1 '@types/istanbul-reports': 3.0.1 - pathe: 0.2.0 + pathe: 1.1.0 packages/expect: specifiers: @@ -742,7 +742,7 @@ importers: fast-glob: ^3.2.12 flatted: ^3.2.7 floating-vue: ^2.0.0-y.0 - pathe: ^1.0.0 + pathe: ^1.1.0 picocolors: ^1.0.0 rollup: ^2.79.1 sirv: ^2.0.2 @@ -757,7 +757,7 @@ importers: dependencies: fast-glob: 3.2.12 flatted: 3.2.7 - pathe: 1.0.0 + pathe: 1.1.0 picocolors: 1.0.0 sirv: 2.0.2 devDependencies: @@ -816,7 +816,7 @@ importers: cac: ^6.7.14 debug: ^4.3.4 mlly: ^1.1.0 - pathe: ^0.2.0 + pathe: ^1.1.0 picocolors: ^1.0.0 rollup: ^2.79.1 source-map: ^0.6.1 @@ -826,7 +826,7 @@ importers: cac: 6.7.14 debug: 4.3.4 mlly: 1.1.0 - pathe: 0.2.0 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 source-map-support: 0.5.21 @@ -881,7 +881,7 @@ importers: mlly: ^1.1.0 natural-compare: ^1.4.0 p-limit: ^4.0.0 - pathe: ^1.0.0 + pathe: ^1.1.0 picocolors: ^1.0.0 pkg-types: ^1.0.1 pretty-format: ^27.5.1 @@ -914,7 +914,7 @@ importers: chai: 4.3.7 debug: 4.3.4 local-pkg: 0.4.2 - pathe: 1.0.0 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 std-env: 3.3.1 @@ -1247,10 +1247,10 @@ importers: test/vite-config: specifiers: - pathe: ^0.2.0 + pathe: ^1.1.0 vitest: workspace:* devDependencies: - pathe: 0.2.0 + pathe: 1.1.0 vitest: link:../../packages/vitest test/vite-node: @@ -7974,7 +7974,7 @@ packages: consola: 2.15.3 fast-glob: 3.2.12 magic-string: 0.27.0 - pathe: 1.0.0 + pathe: 1.1.0 perfect-debounce: 0.1.3 transitivePeerDependencies: - rollup @@ -7996,7 +7996,7 @@ packages: consola: 2.15.3 fast-glob: 3.2.12 magic-string: 0.27.0 - pathe: 1.0.0 + pathe: 1.1.0 perfect-debounce: 0.1.3 transitivePeerDependencies: - rollup @@ -16958,9 +16958,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /pathe/0.2.0: - resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} - /pathe/0.3.9: resolution: {integrity: sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==} dev: true @@ -16968,6 +16965,9 @@ packages: /pathe/1.0.0: resolution: {integrity: sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==} + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + /pathval/1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: false @@ -17121,7 +17121,7 @@ packages: dependencies: jsonc-parser: 3.2.0 mlly: 1.1.0 - pathe: 1.0.0 + pathe: 1.1.0 /playwright-core/1.28.0: resolution: {integrity: sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==} @@ -20267,7 +20267,7 @@ packages: local-pkg: 0.4.2 magic-string: 0.27.0 mlly: 1.1.0 - pathe: 1.0.0 + pathe: 1.1.0 pkg-types: 1.0.1 scule: 1.0.0 strip-literal: 1.0.0 diff --git a/test/vite-config/package.json b/test/vite-config/package.json index f635dace48a5..d5372ea6f1f7 100644 --- a/test/vite-config/package.json +++ b/test/vite-config/package.json @@ -6,7 +6,7 @@ "coverage": "vitest run --coverage" }, "devDependencies": { - "pathe": "^0.2.0", + "pathe": "^1.1.0", "vitest": "workspace:*" } } From 2576144fb7e6f526d0c1eb67a291187dd7fd5282 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 09:40:22 +0100 Subject: [PATCH 27/47] refactor: move rpc methods to entry --- packages/runner/src/collect.ts | 2 +- packages/runner/src/context.ts | 2 +- packages/runner/src/run.ts | 10 +-- packages/runner/src/setup.ts | 2 +- packages/runner/src/types/runner.ts | 63 +++++++++++++++++-- packages/vitest/src/runtime/entry.ts | 37 +++++++++-- .../vitest/src/runtime/runners/benchmark.ts | 18 ++---- packages/vitest/src/runtime/runners/node.ts | 20 ++---- 8 files changed, 109 insertions(+), 45 deletions(-) diff --git a/packages/runner/src/collect.ts b/packages/runner/src/collect.ts index a3d9a66c7bb5..71fee3d41558 100644 --- a/packages/runner/src/collect.ts +++ b/packages/runner/src/collect.ts @@ -36,7 +36,7 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi const collectStart = now() file.setupDuration = collectStart - setupStart - await runner.importFile(filepath) + await runner.importFile(filepath, 'collect') const defaultTasks = await getDefaultSuite().collect(file) diff --git a/packages/runner/src/context.ts b/packages/runner/src/context.ts index c1b20a01735f..40c6706f4d21 100644 --- a/packages/runner/src/context.ts +++ b/packages/runner/src/context.ts @@ -53,7 +53,7 @@ export function createTestContext(test: Test, runner: VitestRunner): TestContext test.onFailed.push(fn) } - return runner.augmentTestContext?.(context) || context + return runner.extendTestContext?.(context) || context } function makeTimeoutMsg(isHook: boolean, timeout: number) { diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index c61c4d742ac3..6e5c814c6592 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -270,8 +270,6 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { } suite.result.duration = now() - start - await runner.onAfterRunSuite?.(suite) - if (suite.mode === 'run') { if (!hasTests(suite)) { suite.result.state = 'fail' @@ -289,6 +287,8 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { } } + await runner.onAfterRunSuite?.(suite) + updateTask(suite, runner) } @@ -317,16 +317,16 @@ export async function runFiles(files: File[], runner: VitestRunner) { } export async function startTests(paths: string[], runner: VitestRunner) { - await runner.onBeforeCollect?.() + await runner.onBeforeCollect?.(paths) const files = await collectTests(paths, runner) runner.onCollected?.(files) - await runner.onBeforeRun?.() + await runner.onBeforeRun?.(files) await runFiles(files, runner) - await runner.onAfterRun?.() + await runner.onAfterRun?.(files) await sendTasksUpdate(runner) diff --git a/packages/runner/src/setup.ts b/packages/runner/src/setup.ts index 0ec0872f1625..ee430eeca2e7 100644 --- a/packages/runner/src/setup.ts +++ b/packages/runner/src/setup.ts @@ -7,7 +7,7 @@ export async function runSetupFiles(config: VitestRunnerConfig, runner: VitestRu files.map(async (fsPath) => { // TODO: check if it's a setup file and remove // getWorkerState().moduleCache.delete(fsPath) - await runner.importFile(fsPath) + await runner.importFile(fsPath, 'setup') }), ) } diff --git a/packages/runner/src/types/runner.ts b/packages/runner/src/types/runner.ts index a5b9a66629e0..35c228204d9c 100644 --- a/packages/runner/src/types/runner.ts +++ b/packages/runner/src/types/runner.ts @@ -17,30 +17,83 @@ export interface VitestRunnerConfig { hookTimeout: number } +export type VitestRunnerImportSource = 'collect' | 'setup' + export interface VitestRunnerConstructor { new (config: VitestRunnerConfig): VitestRunner } export interface VitestRunner { - onBeforeCollect?(): unknown + /** + * First thing that's getting called before actually collecting and running tests. + */ + onBeforeCollect?(paths: string[]): unknown + /** + * Called after collecting tests and before "onBeforeRun". + */ onCollected?(files: File[]): unknown + /** + * Called before running a single test. Doesn't have "result" yet. + */ onBeforeRunTest?(test: Test): unknown + /** + * Called before actually running the test function. Already has "result" with "state" and "startTime". + */ onBeforeTryTest?(test: Test, retryCount: number): unknown + /** + * Called after result and state are set. + */ onAfterRunTest?(test: Test): unknown + /** + * Called right after running the test function. Doesn't have new state yet. Will not be called, if the test function throws. + */ onAfterTryTest?(test: Test, retryCount: number): unknown + /** + * Called before running a single suite. Doesn't have "result" yet. + */ onBeforeRunSuite?(suite: Suite): unknown + /** + * Called after running a single suite. Has state and result. + */ onAfterRunSuite?(suite: Suite): unknown + /** + * If defined, will be called instead of usual Vitest suite partition and handling. + * "before" and "after" hooks will not be ignored. + */ runSuite?(suite: Suite): Promise + /** + * If defined, will be called instead of usual Vitest handling. Useful, if you have your custom test function. + * "before" and "after" hooks will not be ignored. + */ runTest?(test: Test): Promise + /** + * Called, when a task is updated. The same as "onTaskUpdate" in a reporter, but this is running in the same thread as tests. + */ onTaskUpdate?(task: [string, TaskResult | undefined][]): Promise - onBeforeRun?(): unknown - onAfterRun?(): unknown - importFile(filepath: string): unknown - augmentTestContext?(context: TestContext): TestContext + /** + * Called before running all tests in collected paths. + */ + onBeforeRun?(files: File[]): unknown + /** + * Called right after running all tests in collected paths. + */ + onAfterRun?(files: File[]): unknown + /** + * Called when new context for a test is defined. Useful, if you want to add custom properties to the context. + * If you only want to define custom context, consider using "beforeAll" in "setupFiles" instead. + */ + extendTestContext?(context: TestContext): TestContext + /** + * Called, when files are imported. Can be called in two situations: when collecting tests and when importing setup files. + */ + importFile(filepath: string, source: VitestRunnerImportSource): unknown + /** + * Publically available configuration. + */ config: VitestRunnerConfig } diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index 3fe5e40bc296..268977679014 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -1,14 +1,16 @@ import { promises as fs } from 'node:fs' import mm from 'micromatch' -import type { VitestRunnerConstructor } from '@vitest/runner' +import type { VitestRunner, VitestRunnerConstructor } from '@vitest/runner' import { startTests } from '@vitest/runner' import type { EnvironmentOptions, ResolvedConfig, VitestEnvironment } from '../types' import { getWorkerState, resetModules } from '../utils' import { vi } from '../integrations/vi' import { envs } from '../integrations/env' +import { takeCoverageInsideWorker } from '../integrations/coverage' import { setupGlobalEnv, withEnv } from './setup' import { NodeTestRunner } from './runners/node' import { NodeBenchmarkRunner } from './runners/benchmark' +import { rpc } from './rpc' function groupBy(collection: T[], iteratee: (item: T) => K) { return collection.reduce((acc, item) => { @@ -19,21 +21,48 @@ function groupBy(collection: T[], iterate }, {} as Record) } -async function getTestRunner(config: ResolvedConfig): Promise { +async function getTestRunnerConstructor(config: ResolvedConfig): Promise { if (config.runner) // TODO: validation return (await import(config.runner)).default return (config.mode === 'test' ? NodeTestRunner : NodeBenchmarkRunner) as any as VitestRunnerConstructor } +async function getTestRunner(config: ResolvedConfig): Promise { + const TestRunner = await getTestRunnerConstructor(config) + const testRunner = new TestRunner(config) + + // patch some methods, so custom runners don't need to call RPC + const originalOnTaskUpdate = testRunner.onTaskUpdate + testRunner.onTaskUpdate = async (task) => { + const p = rpc().onTaskUpdate(task) + await originalOnTaskUpdate?.call(testRunner, task) + return p + } + + const originalOnCollected = testRunner.onCollected + testRunner.onCollected = async (files) => { + rpc().onCollected(files) + await originalOnCollected?.call(testRunner, files) + } + + const originalOnAfterRun = testRunner.onAfterRun + testRunner.onAfterRun = async (files) => { + const coverage = await takeCoverageInsideWorker(config.coverage) + rpc().onAfterSuiteRun({ coverage }) + await originalOnAfterRun?.call(testRunner, files) + } + + return testRunner +} + // browser shouldn't call this! export async function run(files: string[], config: ResolvedConfig): Promise { await setupGlobalEnv(config) const workerState = getWorkerState() - const TestRunner = await getTestRunner(config) - const testRunner = new TestRunner(config) + const testRunner = await getTestRunner(config) // if calling from a worker, there will always be one file // if calling with no-threads, this will be the whole suite diff --git a/packages/vitest/src/runtime/runners/benchmark.ts b/packages/vitest/src/runtime/runners/benchmark.ts index c07e4efe3864..1761688f6538 100644 --- a/packages/vitest/src/runtime/runners/benchmark.ts +++ b/packages/vitest/src/runtime/runners/benchmark.ts @@ -1,9 +1,9 @@ import { performance } from 'node:perf_hooks' -import type { File, Suite, Task, TaskResult, VitestRunner } from '@vitest/runner' +import type { Suite, Task, VitestRunner, VitestRunnerImportSource } from '@vitest/runner' import { updateTask as updateRunnerTask } from '@vitest/runner' import { createDefer, getSafeTimers } from '@vitest/utils' -import { rpc } from '../rpc' import { getBenchFn, getBenchOptions } from '../benchmark' +import { getWorkerState } from '../../utils' import type { BenchTask, Benchmark, BenchmarkResult, ResolvedConfig } from '#types' async function importTinybench() { @@ -121,23 +121,15 @@ async function runBenchmarkSuite(suite: Suite, runner: VitestRunner) { } export class NodeBenchmarkRunner implements VitestRunner { - private rpc = rpc() - constructor(public config: ResolvedConfig) {} - importFile(filepath: string): unknown { + importFile(filepath: string, source: VitestRunnerImportSource): unknown { + if (source === 'setup') + getWorkerState().moduleCache.delete(filepath) return import(filepath) } - onCollected(files: File[]) { - this.rpc.onCollected(files) - } - async runSuite(suite: Suite): Promise { await runBenchmarkSuite(suite, this) } - - onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { - return this.rpc.onTaskUpdate(task) - } } diff --git a/packages/vitest/src/runtime/runners/node.ts b/packages/vitest/src/runtime/runners/node.ts index e256d65b0a6a..20c554118d3b 100644 --- a/packages/vitest/src/runtime/runners/node.ts +++ b/packages/vitest/src/runtime/runners/node.ts @@ -1,10 +1,8 @@ import process from 'node:process' -import type { File, Suite, TaskResult, Test, TestContext, VitestRunner } from '@vitest/runner' +import type { Suite, Test, TestContext, VitestRunner, VitestRunnerImportSource } from '@vitest/runner' import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect' -import { rpc } from '../rpc' import { getSnapshotClient } from '../../integrations/snapshot/chai' import { vi } from '../../integrations/vi' -import { takeCoverageInsideWorker } from '../../integrations/coverage' import { getFullName, getWorkerState } from '../../utils' import { createExpect } from '../../integrations/chai/index' import type { ResolvedConfig } from '#types' @@ -15,21 +13,17 @@ export class NodeTestRunner implements VitestRunner { constructor(public config: ResolvedConfig) {} - importFile(filepath: string): unknown { + importFile(filepath: string, source: VitestRunnerImportSource): unknown { + if (source === 'setup') + this.workerState.moduleCache.delete(filepath) return import(filepath) } - onCollected(files: File[]) { - rpc().onCollected(files) - } - onBeforeRun() { this.snapshotClient.clear() } async onAfterRun() { - const coverage = await takeCoverageInsideWorker(this.config.coverage) - rpc().onAfterSuiteRun({ coverage }) await this.snapshotClient.saveCurrent() } @@ -89,7 +83,7 @@ export class NodeTestRunner implements VitestRunner { throw isExpectingAssertionsError } - augmentTestContext(context: TestContext): TestContext { + extendTestContext(context: TestContext): TestContext { let _expect: Vi.ExpectStatic | undefined Object.defineProperty(context, 'expect', { get() { @@ -105,10 +99,6 @@ export class NodeTestRunner implements VitestRunner { }) return context } - - onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { - return rpc().onTaskUpdate(task) - } } function clearModuleMocks(config: ResolvedConfig) { From 5efbfb43610080f78d296924d4193420c948efba Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 09:45:46 +0100 Subject: [PATCH 28/47] test: fix reporter tests --- test/reporters/src/context.ts | 2 +- .../tests/__snapshots__/reporters.spec.ts.snap | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/reporters/src/context.ts b/test/reporters/src/context.ts index 38c67c54067d..69276ca7e2e8 100644 --- a/test/reporters/src/context.ts +++ b/test/reporters/src/context.ts @@ -13,7 +13,7 @@ export function getContext(): Context { let output = '' const config: Partial = { - root: '/', + root: '/vitest', } const moduleGraph: Partial = { diff --git a/test/reporters/tests/__snapshots__/reporters.spec.ts.snap b/test/reporters/tests/__snapshots__/reporters.spec.ts.snap index e3ba742d5659..6ec2907cdcb3 100644 --- a/test/reporters/tests/__snapshots__/reporters.spec.ts.snap +++ b/test/reporters/tests/__snapshots__/reporters.spec.ts.snap @@ -7,7 +7,7 @@ exports[`JUnit reporter (no outputFile entry) 1`] = ` AssertionError: expected 2.23606797749979 to equal 2 - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 @@ -43,7 +43,7 @@ exports[`JUnit reporter 1`] = ` AssertionError: expected 2.23606797749979 to equal 2 - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 @@ -84,7 +84,7 @@ exports[`JUnit reporter with outputFile 2`] = ` AssertionError: expected 2.23606797749979 to equal 2 - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 @@ -125,7 +125,7 @@ exports[`JUnit reporter with outputFile in non-existing directory 2`] = ` AssertionError: expected 2.23606797749979 to equal 2 - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 @@ -166,7 +166,7 @@ exports[`JUnit reporter with outputFile object 2`] = ` AssertionError: expected 2.23606797749979 to equal 2 - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 @@ -207,7 +207,7 @@ exports[`JUnit reporter with outputFile object in non-existing directory 2`] = ` AssertionError: expected 2.23606797749979 to equal 2 - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 @@ -248,7 +248,7 @@ exports[`JUnit reporter with outputFile with XML in error message 2`] = ` AssertionError: error message that has XML in it <tag> - ❯ vitest/test/core/test/basic.test.ts:8:32 + ❯ test/core/test/basic.test.ts:8:32 From 6fe7876a0798563e27d5d268ddd3b062ad43df06 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 09:56:49 +0100 Subject: [PATCH 29/47] fix: show browser tests --- packages/browser/src/client/runner.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts index a84aa70a09df..1258e03ff1c3 100644 --- a/packages/browser/src/client/runner.ts +++ b/packages/browser/src/client/runner.ts @@ -1,4 +1,4 @@ -import type { TaskResult, VitestRunner } from '@vitest/runner' +import type { File, TaskResult, VitestRunner } from '@vitest/runner' import type { VitestClient } from '@vitest/ws-client' import type { ResolvedConfig } from '#types' @@ -19,6 +19,10 @@ export class BrowserTestRunner implements VitestRunner { this.client = options.client } + onCollected(files: File[]): unknown { + return this.client.rpc.onCollected(files) + } + onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { return this.client.rpc.onTaskUpdate(task) } From 2e21b74043974191f38a10d8a512acce8b1923f8 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 09:56:57 +0100 Subject: [PATCH 30/47] chore: add cypress alias --- packages/ui/cypress.config.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ui/cypress.config.ts b/packages/ui/cypress.config.ts index cf9541384a02..040f6e98a40f 100644 --- a/packages/ui/cypress.config.ts +++ b/packages/ui/cypress.config.ts @@ -12,8 +12,10 @@ export default defineConfig({ define: { 'process.env.NODE_DEBUG': '"false"', }, - alias: { - '@vitest/runner/utils': resolve('../runner/src/utils.ts'), + resolve: { + alias: { + '@vitest/runner/utils': resolve('../runner/src/utils.ts'), + }, }, configFile: resolve('./cypress/vite.config.ts'), }, From dc7f35c093b1d2a86058595742c246e1164c4808 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 11:10:45 +0100 Subject: [PATCH 31/47] refactor: use picocolors only in Node --- packages/browser/src/client/main.ts | 22 +-- packages/browser/src/client/vite.config.ts | 3 + packages/browser/src/node/index.ts | 3 + packages/expect/package.json | 4 +- packages/expect/rollup.config.js | 1 + packages/expect/src/index.ts | 1 + packages/expect/src/jest-expect.ts | 17 +- packages/expect/src/jest-extend.ts | 6 +- packages/expect/src/jest-matcher-utils.ts | 156 ++++++++++--------- packages/expect/src/types.ts | 6 +- packages/ui/cypress.config.ts | 2 +- packages/utils/src/colors.ts | 46 ++++++ packages/utils/src/constants.ts | 1 + packages/utils/src/index.ts | 1 + packages/vitest/browser.d.ts | 1 + packages/vitest/package.json | 4 + packages/vitest/rollup.config.js | 2 + packages/vitest/src/browser.ts | 1 + packages/vitest/src/node/cli-api.ts | 2 +- packages/vitest/src/node/pkg.ts | 36 +++++ packages/vitest/src/node/plugins/index.ts | 3 +- packages/vitest/src/node/reporters/utils.ts | 2 +- packages/vitest/src/runtime/mocker.ts | 3 +- packages/vitest/src/runtime/setup.ts | 4 +- packages/vitest/src/typecheck/typechecker.ts | 3 +- packages/vitest/src/utils/index.ts | 70 +-------- pnpm-lock.yaml | 1 + 27 files changed, 229 insertions(+), 172 deletions(-) create mode 100644 packages/utils/src/colors.ts create mode 100644 packages/vitest/browser.d.ts create mode 100644 packages/vitest/src/browser.ts create mode 100644 packages/vitest/src/node/pkg.ts diff --git a/packages/browser/src/client/main.ts b/packages/browser/src/client/main.ts index f86a965d45e8..9103fcb28a11 100644 --- a/packages/browser/src/client/main.ts +++ b/packages/browser/src/client/main.ts @@ -19,22 +19,7 @@ let config: ResolvedConfig | undefined let runner: VitestRunner | undefined const browserHashMap = new Map() -export const client = createClient(ENTRY_URL, { - handlers: { - async onPathsCollected(paths) { - if (!paths) - return - - // const config = __vitest_worker__.config - const now = `${new Date().getTime()}` - paths.forEach((i) => { - browserHashMap.set(i, now) - }) - - await runTests(paths, config, client) - }, - }, -}) +export const client = createClient(ENTRY_URL) const ws = client.ws @@ -79,7 +64,10 @@ ws.addEventListener('open', async () => { }) async function runTests(paths: string[], config: any, client: VitestClient) { - const { startTests } = await import('@vitest/runner') + // we use dynamic import here, because this file is bundled with UI, + // but we need to resolve correct path at runtime + const path = '/__vitest_index__' + const { startTests } = await import(path) as typeof import('vitest/browser') if (!runner) runner = new BrowserTestRunner({ config, client, browserHashMap }) diff --git a/packages/browser/src/client/vite.config.ts b/packages/browser/src/client/vite.config.ts index 4c60bfe5acc0..139c3bd57a60 100644 --- a/packages/browser/src/client/vite.config.ts +++ b/packages/browser/src/client/vite.config.ts @@ -8,5 +8,8 @@ export default defineConfig({ minify: false, outDir: '../../dist/client', emptyOutDir: false, + rollupOptions: { + external: ['/__vitest_index__'], + }, }, }) diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index 859c299d04b7..7c64bdc074b3 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -35,6 +35,9 @@ export default (base = '/'): Plugin[] => { if (ctx.ssr) return + if (id === '/__vitest_index__') + return this.resolve('vitest/browser') + if (stubs.includes(id)) return resolve(pkgRoot, 'stubs', id) diff --git a/packages/expect/package.json b/packages/expect/package.json index 6d290030dc09..ca036c0c3e3d 100644 --- a/packages/expect/package.json +++ b/packages/expect/package.json @@ -31,7 +31,9 @@ "dependencies": { "@vitest/spy": "workspace:*", "@vitest/utils": "workspace:*", - "chai": "^4.3.7", + "chai": "^4.3.7" + }, + "devDependencies": { "picocolors": "^1.0.0" } } diff --git a/packages/expect/rollup.config.js b/packages/expect/rollup.config.js index fddfda5974ce..dbaa1b71eb00 100644 --- a/packages/expect/rollup.config.js +++ b/packages/expect/rollup.config.js @@ -8,6 +8,7 @@ const external = [ ...builtinModules, ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), + '@vitest/utils/diff', ] const plugins = [ diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index e472669a1e1e..f57106312f83 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -5,3 +5,4 @@ export * from './types' export { getState, setState } from './state' export { JestChaiExpect } from './jest-expect' export { JestExtend } from './jest-extend' +export { setColors as setupColors } from '@vitest/utils' diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index cb7372c66f30..30f135865574 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1,6 +1,5 @@ -import c from 'picocolors' import { AssertionError } from 'chai' -import { assertTypes } from '@vitest/utils' +import { assertTypes, getColors } from '@vitest/utils' import type { Constructable } from '@vitest/utils' import type { EnhancedSpy } from '@vitest/spy' import { isMockFunction } from '@vitest/spy' @@ -12,6 +11,8 @@ import { JEST_MATCHERS_OBJECT } from './constants' // Jest Expect Compact export const JestChaiExpect: ChaiPlugin = (chai, utils) => { + const c = () => getColors() + function def(name: keyof Vi.Assertion | (keyof Vi.Assertion)[], fn: ((this: Chai.AssertionStatic & Vi.Assertion, ...args: any[]) => any)) { const addMethod = (n: keyof Vi.Assertion) => { utils.addMethod(chai.Assertion.prototype, n, fn) @@ -351,8 +352,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { return `${i}th` } const formatCalls = (spy: EnhancedSpy, msg: string, actualCall?: any) => { - msg += c.gray(`\n\nReceived: \n${spy.mock.calls.map((callArg, i) => { - let methodCall = c.bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call:\n\n`) + msg += c().gray(`\n\nReceived: \n${spy.mock.calls.map((callArg, i) => { + let methodCall = c().bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call:\n\n`) if (actualCall) methodCall += diff(callArg, actualCall, { showLegend: false }) else @@ -361,12 +362,12 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { methodCall += '\n' return methodCall }).join('\n')}`) - msg += c.gray(`\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`) + msg += c().gray(`\n\nNumber of calls: ${c().bold(spy.mock.calls.length)}\n`) return msg } const formatReturns = (spy: EnhancedSpy, msg: string, actualReturn?: any) => { - msg += c.gray(`\n\nReceived: \n${spy.mock.results.map((callReturn, i) => { - let methodCall = c.bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call return:\n\n`) + msg += c().gray(`\n\nReceived: \n${spy.mock.results.map((callReturn, i) => { + let methodCall = c().bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call return:\n\n`) if (actualReturn) methodCall += diff(callReturn.value, actualReturn, { showLegend: false }) else @@ -375,7 +376,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { methodCall += '\n' return methodCall }).join('\n')}`) - msg += c.gray(`\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`) + msg += c().gray(`\n\nNumber of calls: ${c().bold(spy.mock.calls.length)}\n`) return msg } def(['toHaveBeenCalledTimes', 'toBeCalledTimes'], function (number: number) { diff --git a/packages/expect/src/jest-extend.ts b/packages/expect/src/jest-extend.ts index a7d9795668ff..5651b578f1cf 100644 --- a/packages/expect/src/jest-extend.ts +++ b/packages/expect/src/jest-extend.ts @@ -9,7 +9,7 @@ import { JEST_MATCHERS_OBJECT } from './constants' import { AsymmetricMatcher } from './jest-asymmetric-matchers' import { getState } from './state' -import * as matcherUtils from './jest-matcher-utils' +import { diff, getMatcherUtils, stringify } from './jest-matcher-utils' import { equals, @@ -22,7 +22,9 @@ const getMatcherState = (assertion: Chai.AssertionStatic & Chai.Assertion, expec const isNot = util.flag(assertion, 'negate') as boolean const promise = util.flag(assertion, 'promise') || '' const jestUtils = { - ...matcherUtils, + ...getMatcherUtils(), + diff, + stringify, iterableEquality, subsetEquality, } diff --git a/packages/expect/src/jest-matcher-utils.ts b/packages/expect/src/jest-matcher-utils.ts index ec9bfe7bd068..c71c3302b111 100644 --- a/packages/expect/src/jest-matcher-utils.ts +++ b/packages/expect/src/jest-matcher-utils.ts @@ -1,94 +1,110 @@ -import c from 'picocolors' -import { stringify } from '@vitest/utils' +import { getColors, stringify } from '@vitest/utils' import { unifiedDiff } from '@vitest/utils/diff' import type { DiffOptions, MatcherHintOptions } from './types' export { stringify } -export const EXPECTED_COLOR = c.green -export const RECEIVED_COLOR = c.red -export const INVERTED_COLOR = c.inverse -export const BOLD_WEIGHT = c.bold -export const DIM_COLOR = c.dim - -export function matcherHint( - matcherName: string, - received = 'received', - expected = 'expected', - options: MatcherHintOptions = {}, -) { - const { - comment = '', - isDirectExpectCall = false, // seems redundant with received === '' - isNot = false, - promise = '', - secondArgument = '', - expectedColor = EXPECTED_COLOR, - receivedColor = RECEIVED_COLOR, - secondArgumentColor = EXPECTED_COLOR, - } = options - let hint = '' - let dimString = 'expect' // concatenate adjacent dim substrings - - if (!isDirectExpectCall && received !== '') { - hint += DIM_COLOR(`${dimString}(`) + receivedColor(received) - dimString = ')' - } +export function getMatcherUtils() { + const c = () => getColors() - if (promise !== '') { - hint += DIM_COLOR(`${dimString}.`) + promise - dimString = '' - } + const EXPECTED_COLOR = c().green + const RECEIVED_COLOR = c().red + const INVERTED_COLOR = c().inverse + const BOLD_WEIGHT = c().bold + const DIM_COLOR = c().dim - if (isNot) { - hint += `${DIM_COLOR(`${dimString}.`)}not` - dimString = '' - } + function matcherHint( + matcherName: string, + received = 'received', + expected = 'expected', + options: MatcherHintOptions = {}, + ) { + const { + comment = '', + isDirectExpectCall = false, // seems redundant with received === '' + isNot = false, + promise = '', + secondArgument = '', + expectedColor = EXPECTED_COLOR, + receivedColor = RECEIVED_COLOR, + secondArgumentColor = EXPECTED_COLOR, + } = options + let hint = '' + let dimString = 'expect' // concatenate adjacent dim substrings + + if (!isDirectExpectCall && received !== '') { + hint += DIM_COLOR(`${dimString}(`) + receivedColor(received) + dimString = ')' + } - if (matcherName.includes('.')) { + if (promise !== '') { + hint += DIM_COLOR(`${dimString}.`) + promise + dimString = '' + } + + if (isNot) { + hint += `${DIM_COLOR(`${dimString}.`)}not` + dimString = '' + } + + if (matcherName.includes('.')) { // Old format: for backward compatibility, // especially without promise or isNot options - dimString += matcherName - } - else { + dimString += matcherName + } + else { // New format: omit period from matcherName arg - hint += DIM_COLOR(`${dimString}.`) + matcherName - dimString = '' - } + hint += DIM_COLOR(`${dimString}.`) + matcherName + dimString = '' + } - if (expected === '') { - dimString += '()' - } - else { - hint += DIM_COLOR(`${dimString}(`) + expectedColor(expected) - if (secondArgument) - hint += DIM_COLOR(', ') + secondArgumentColor(secondArgument) - dimString = ')' - } + if (expected === '') { + dimString += '()' + } + else { + hint += DIM_COLOR(`${dimString}(`) + expectedColor(expected) + if (secondArgument) + hint += DIM_COLOR(', ') + secondArgumentColor(secondArgument) + dimString = ')' + } - if (comment !== '') - dimString += ` // ${comment}` + if (comment !== '') + dimString += ` // ${comment}` - if (dimString !== '') - hint += DIM_COLOR(dimString) + if (dimString !== '') + hint += DIM_COLOR(dimString) - return hint -} + return hint + } -const SPACE_SYMBOL = '\u{00B7}' // middle dot + const SPACE_SYMBOL = '\u{00B7}' // middle dot -// Instead of inverse highlight which now implies a change, -// replace common spaces with middle dot at the end of any line. -const replaceTrailingSpaces = (text: string): string => - text.replace(/\s+$/gm, spaces => SPACE_SYMBOL.repeat(spaces.length)) + // Instead of inverse highlight which now implies a change, + // replace common spaces with middle dot at the end of any line. + const replaceTrailingSpaces = (text: string): string => + text.replace(/\s+$/gm, spaces => SPACE_SYMBOL.repeat(spaces.length)) -export const printReceived = (object: unknown): string => - RECEIVED_COLOR(replaceTrailingSpaces(stringify(object))) -export const printExpected = (value: unknown): string => - EXPECTED_COLOR(replaceTrailingSpaces(stringify(value))) + const printReceived = (object: unknown): string => + RECEIVED_COLOR(replaceTrailingSpaces(stringify(object))) + const printExpected = (value: unknown): string => + EXPECTED_COLOR(replaceTrailingSpaces(stringify(value))) + + return { + EXPECTED_COLOR, + RECEIVED_COLOR, + INVERTED_COLOR, + BOLD_WEIGHT, + DIM_COLOR, + + matcherHint, + printReceived, + printExpected, + } +} // TODO: do something with options export function diff(a: any, b: any, options?: DiffOptions) { + const c = getColors() return unifiedDiff(stringify(b), stringify(a), { colorDim: c.dim, colorSuccess: c.green, diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 01a3a85a8b00..1b6ae780b757 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -9,7 +9,7 @@ import type { use as chaiUse } from 'chai' */ import type { Formatter } from 'picocolors/types' -import type * as jestMatcherUtils from './jest-matcher-utils' +import type { diff, getMatcherUtils, stringify } from './jest-matcher-utils' export type FirstFunctionArgument = T extends (arg: infer A) => unknown ? A : never export type ChaiPlugin = FirstFunctionArgument @@ -72,7 +72,9 @@ export interface MatcherState { // snapshotState: SnapshotState suppressedErrors: Array testPath?: string - utils: typeof jestMatcherUtils & { + utils: ReturnType & { + diff: typeof diff + stringify: typeof stringify iterableEquality: Tester subsetEquality: Tester } diff --git a/packages/ui/cypress.config.ts b/packages/ui/cypress.config.ts index 040f6e98a40f..4ed52d9d0a86 100644 --- a/packages/ui/cypress.config.ts +++ b/packages/ui/cypress.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ }, resolve: { alias: { - '@vitest/runner/utils': resolve('../runner/src/utils.ts'), + '@vitest/runner/utils': resolve('../runner/src/utils/index.ts'), }, }, configFile: resolve('./cypress/vite.config.ts'), diff --git a/packages/utils/src/colors.ts b/packages/utils/src/colors.ts new file mode 100644 index 000000000000..6c31d625d98f --- /dev/null +++ b/packages/utils/src/colors.ts @@ -0,0 +1,46 @@ +import type p from 'picocolors' +import type { Formatter } from 'picocolors/types' +import { SAFE_COLORS_SYMBOL } from './constants' + +const colors = [ + 'reset', + 'bold', + 'dim', + 'italic', + 'underline', + 'inverse', + 'hidden', + 'strikethrough', + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'magenta', + 'cyan', + 'white', + 'gray', + 'bgBlack', + 'bgRed', + 'bgGreen', + 'bgYellow', + 'bgBlue', + 'bgMagenta', + 'bgCyan', + 'bgWhite', +] as const + +const formatter: Formatter = str => String(str) + +const defaultColors = colors.reduce((acc, key) => { + acc[key] = formatter + return acc +}, { isColorSupported: false } as typeof p) + +export function getColors(): typeof p { + return (globalThis as any)[SAFE_COLORS_SYMBOL] || defaultColors +} + +export function setColors(colors: typeof p) { + (globalThis as any)[SAFE_COLORS_SYMBOL] = colors +} diff --git a/packages/utils/src/constants.ts b/packages/utils/src/constants.ts index 04a602aa1405..1b285a267dab 100644 --- a/packages/utils/src/constants.ts +++ b/packages/utils/src/constants.ts @@ -1 +1,2 @@ export const SAFE_TIMERS_SYMBOL = Symbol('vitest:SAFE_TIMERS') +export const SAFE_COLORS_SYMBOL = Symbol('vitest:SAFE_COLORS') diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index e91254aa2a84..953be9159075 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -5,3 +5,4 @@ export * from './timers' export * from './random' export * from './display' export * from './constants' +export * from './colors' diff --git a/packages/vitest/browser.d.ts b/packages/vitest/browser.d.ts new file mode 100644 index 000000000000..81bff543e8b8 --- /dev/null +++ b/packages/vitest/browser.d.ts @@ -0,0 +1 @@ +export * from './dist/browser.js' diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 8d95b40511f8..d90914df9ecb 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -45,6 +45,10 @@ "types": "./dist/node.d.ts", "import": "./dist/node.js" }, + "./browser": { + "types": "./dist/browser.d.ts", + "import": "./dist/browser.js" + }, "./environments": { "types": "./dist/environments.d.ts", "import": "./dist/environments.js" diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index bcbbfb2523c9..4188b6335fc9 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -18,6 +18,7 @@ const entries = [ 'src/node/cli.ts', 'src/node/cli-wrapper.ts', 'src/node.ts', + 'src/browser.ts', 'src/environments.ts', 'src/runtime/worker.ts', 'src/runtime/loader.ts', @@ -29,6 +30,7 @@ const dtsEntries = [ 'src/index.ts', 'src/node.ts', 'src/environments.ts', + 'src/browser.ts', 'src/config.ts', ] diff --git a/packages/vitest/src/browser.ts b/packages/vitest/src/browser.ts new file mode 100644 index 000000000000..62fef668df77 --- /dev/null +++ b/packages/vitest/src/browser.ts @@ -0,0 +1 @@ +export { startTests } from '@vitest/runner' diff --git a/packages/vitest/src/node/cli-api.ts b/packages/vitest/src/node/cli-api.ts index 8f104a10697e..62c245d3b8bc 100644 --- a/packages/vitest/src/node/cli-api.ts +++ b/packages/vitest/src/node/cli-api.ts @@ -4,7 +4,7 @@ import { EXIT_CODE_RESTART } from '../constants' import { CoverageProviderMap } from '../integrations/coverage' import { getEnvPackageName } from '../integrations/env' import type { UserConfig, Vitest, VitestRunMode } from '../types' -import { ensurePackageInstalled } from '../utils' +import { ensurePackageInstalled } from './pkg' import { createVitest } from './create' import { registerConsoleShortcuts } from './stdin' diff --git a/packages/vitest/src/node/pkg.ts b/packages/vitest/src/node/pkg.ts new file mode 100644 index 000000000000..f0775e93237c --- /dev/null +++ b/packages/vitest/src/node/pkg.ts @@ -0,0 +1,36 @@ +import c from 'picocolors' +import { isPackageExists } from 'local-pkg' +import { EXIT_CODE_RESTART } from '../constants' +import { isCI } from '../utils/env' + +export async function ensurePackageInstalled( + dependency: string, + root: string, +) { + if (isPackageExists(dependency, { paths: [root] })) + return true + + const promptInstall = !isCI && process.stdout.isTTY + + process.stderr.write(c.red(`${c.inverse(c.red(' MISSING DEP '))} Can not find dependency '${dependency}'\n\n`)) + + if (!promptInstall) + return false + + const prompts = await import('prompts') + const { install } = await prompts.prompt({ + type: 'confirm', + name: 'install', + message: c.reset(`Do you want to install ${c.green(dependency)}?`), + }) + + if (install) { + await (await import('@antfu/install-pkg')).installPackage(dependency, { dev: true }) + // TODO: somehow it fails to load the package after installation, remove this when it's fixed + process.stderr.write(c.yellow(`\nPackage ${dependency} installed, re-run the command to start.\n`)) + process.exit(EXIT_CODE_RESTART) + return true + } + + return false +} diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index 1e6bd5f97690..ff525f526fc5 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -2,7 +2,8 @@ import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite' import { relative } from 'pathe' import { configDefaults } from '../../defaults' import type { ResolvedConfig, UserConfig } from '../../types' -import { deepMerge, ensurePackageInstalled, notNullish, removeUndefinedValues } from '../../utils' +import { deepMerge, notNullish, removeUndefinedValues } from '../../utils' +import { ensurePackageInstalled } from '../pkg' import { resolveApiConfig } from '../config' import { Vitest } from '../core' import { generateScopedClassName } from '../../integrations/css/css-modules' diff --git a/packages/vitest/src/node/reporters/utils.ts b/packages/vitest/src/node/reporters/utils.ts index 0931c481e947..5a7fbc0a2f2c 100644 --- a/packages/vitest/src/node/reporters/utils.ts +++ b/packages/vitest/src/node/reporters/utils.ts @@ -1,6 +1,6 @@ import type { ViteNodeRunner } from 'vite-node/client' import type { Reporter } from '../../types' -import { ensurePackageInstalled } from '../../utils' +import { ensurePackageInstalled } from '../pkg' import { BenchmarkReportsMap, ReportersMap } from './index' import type { BenchmarkBuiltinReporters, BuiltinReporters } from './index' diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index 4c9ba61e9d8d..747707242b35 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -1,7 +1,7 @@ import { existsSync, readdirSync } from 'node:fs' import { isNodeBuiltin } from 'mlly' import { basename, dirname, extname, isAbsolute, join, resolve } from 'pathe' -import c from 'picocolors' +import { getColors } from '@vitest/utils' import { getAllMockableProperties, getType, getWorkerState } from '../utils' import { distDir } from '../constants' import type { MockFactory, PendingSuiteMock } from '../types/mocker' @@ -134,6 +134,7 @@ export class VitestMocker { return target.then.bind(target) } else if (!(prop in target)) { + const c = getColors() throw new Error( `[vitest] No "${String(prop)}" export is defined on the "${mockpath}" mock. ` + 'Did you forget to return it from "vi.mock"?' diff --git a/packages/vitest/src/runtime/setup.ts b/packages/vitest/src/runtime/setup.ts index eb1b026c76cc..fb5794e0894c 100644 --- a/packages/vitest/src/runtime/setup.ts +++ b/packages/vitest/src/runtime/setup.ts @@ -1,7 +1,8 @@ /* eslint-disable n/no-deprecated-api */ +import p from 'picocolors' import { installSourcemapsSupport } from 'vite-node/source-map' -import { setSafeTimers } from '@vitest/utils' +import { setColors, setSafeTimers } from '@vitest/utils' import { environments } from '../integrations/env' import type { Environment, ResolvedConfig } from '../types' import { getSafeTimers, getWorkerState, isNode } from '../utils' @@ -27,6 +28,7 @@ export async function setupGlobalEnv(config: ResolvedConfig) { if (globalSetup) return + setColors(p) setSafeTimers() globalSetup = true diff --git a/packages/vitest/src/typecheck/typechecker.ts b/packages/vitest/src/typecheck/typechecker.ts index c8590b03193e..20d68fec0973 100644 --- a/packages/vitest/src/typecheck/typechecker.ts +++ b/packages/vitest/src/typecheck/typechecker.ts @@ -3,7 +3,8 @@ import type { ExecaChildProcess } from 'execa' import { execa } from 'execa' import { extname, resolve } from 'pathe' import { SourceMapConsumer } from 'source-map' -import { ensurePackageInstalled, getTasks } from '../utils' +import { getTasks } from '../utils' +import { ensurePackageInstalled } from '../node/pkg' import type { Awaitable, File, ParsedStack, Task, TaskResultPack, TaskState, TscErrorInfo, Vitest } from '../types' import { getRawErrsMapFromTsCompile, getTsconfig } from './parse' import { createIndexMap } from './utils' diff --git a/packages/vitest/src/utils/index.ts b/packages/vitest/src/utils/index.ts index 544824559eda..cfea2032a5a7 100644 --- a/packages/vitest/src/utils/index.ts +++ b/packages/vitest/src/utils/index.ts @@ -1,12 +1,8 @@ -import { relative as relativeBrowser } from 'node:path' -import c from 'picocolors' -import type { Suite, Task } from '@vitest/runner' -import { isPackageExists } from 'local-pkg' -import { relative as relativeNode } from 'pathe' +// import { relative as relativeBrowser } from 'node:path' +import { relative } from 'pathe' import type { ModuleCacheMap } from 'vite-node' -import { EXIT_CODE_RESTART } from '../constants' import { getWorkerState } from '../utils' -import { isBrowser, isCI, isNode } from './env' +import { isNode } from './env' export * from './graph' export * from './tasks' @@ -21,28 +17,8 @@ export const getRunMode = () => getWorkerState().config.mode export const isRunningInTest = () => getRunMode() === 'test' export const isRunningInBenchmark = () => getRunMode() === 'benchmark' -export const relativePath = isBrowser ? relativeBrowser : relativeNode - -/** - * Partition in tasks groups by consecutive concurrent - */ -export function partitionSuiteChildren(suite: Suite) { - let tasksGroup: Task[] = [] - const tasksGroups: Task[][] = [] - for (const c of suite.tasks) { - if (tasksGroup.length === 0 || c.concurrent === tasksGroup[0].concurrent) { - tasksGroup.push(c) - } - else { - tasksGroups.push(tasksGroup) - tasksGroup = [c] - } - } - if (tasksGroup.length > 0) - tasksGroups.push(tasksGroup) - - return tasksGroups -} +export const relativePath = relative +export { resolve } from 'pathe' export function resetModules(modules: ModuleCacheMap, resetMocks = false) { const skipPaths = [ @@ -71,38 +47,6 @@ export function removeUndefinedValues>(obj: T): T return obj } -export async function ensurePackageInstalled( - dependency: string, - root: string, -) { - if (isPackageExists(dependency, { paths: [root] })) - return true - - const promptInstall = !isCI && process.stdout.isTTY - - process.stderr.write(c.red(`${c.inverse(c.red(' MISSING DEP '))} Can not find dependency '${dependency}'\n\n`)) - - if (!promptInstall) - return false - - const prompts = await import('prompts') - const { install } = await prompts.prompt({ - type: 'confirm', - name: 'install', - message: c.reset(`Do you want to install ${c.green(dependency)}?`), - }) - - if (install) { - await (await import('@antfu/install-pkg')).installPackage(dependency, { dev: true }) - // TODO: somehow it fails to load the package after installation, remove this when it's fixed - process.stderr.write(c.yellow(`\nPackage ${dependency} installed, re-run the command to start.\n`)) - process.exit(EXIT_CODE_RESTART) - return true - } - - return false -} - /** * If code starts with a function call, will return its last index, respecting arguments. * This will return 25 - last ending character of toMatch ")" @@ -145,10 +89,6 @@ export function getCallLastIndex(code: string) { return null } -const resolve = isNode ? relativeNode : relativeBrowser - -export { resolve as resolvePath } - // AggregateError is supported in Node.js 15.0.0+ class AggregateErrorPonyfill extends Error { errors: unknown[] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14a9bdb07690..a3ffdbde7696 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -702,6 +702,7 @@ importers: '@vitest/spy': link:../spy '@vitest/utils': link:../utils chai: 4.3.7 + devDependencies: picocolors: 1.0.0 packages/runner: From 87104dd3cc98d5145edec5d152c6b6546d1c5af9 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 12:30:24 +0100 Subject: [PATCH 32/47] fix: allow snapshot testing in browser --- .eslintrc | 9 ---- packages/browser/src/client/main.ts | 34 +++++++++++-- packages/browser/src/client/runner.ts | 49 ++++++++++--------- packages/browser/src/client/snapshot.ts | 26 ++++++++++ packages/browser/src/client/vite.config.ts | 3 -- packages/browser/src/node/index.ts | 3 ++ packages/vitest/package.json | 4 ++ packages/vitest/rollup.config.js | 2 + packages/vitest/runners.d.ts | 1 + packages/vitest/src/api/setup.ts | 18 ++++++- packages/vitest/src/api/types.ts | 8 ++- packages/vitest/src/browser.ts | 2 + packages/vitest/src/index.ts | 1 + .../src/integrations/snapshot/client.ts | 4 +- .../vitest/src/integrations/snapshot/env.ts | 19 +++++++ .../snapshot/environments/node.ts | 28 +++++++++++ .../snapshot/port/inlineSnapshot.ts | 7 +-- .../src/integrations/snapshot/port/state.ts | 34 ++++++++++--- .../src/integrations/snapshot/port/utils.ts | 27 +++++----- packages/vitest/src/runners.ts | 2 + packages/vitest/src/runtime/entry.ts | 6 +-- .../vitest/src/runtime/runners/benchmark.ts | 1 - .../src/runtime/runners/{node.ts => test.ts} | 7 ++- packages/vitest/src/runtime/setup.common.ts | 23 +++++++++ .../src/runtime/{setup.ts => setup.node.ts} | 37 +++++--------- .../test/__snapshots__/snapshot.test.ts.snap | 3 ++ test/browser/test/snapshot.test.ts | 9 ++++ 27 files changed, 265 insertions(+), 102 deletions(-) create mode 100644 packages/browser/src/client/snapshot.ts create mode 100644 packages/vitest/runners.d.ts create mode 100644 packages/vitest/src/integrations/snapshot/env.ts create mode 100644 packages/vitest/src/integrations/snapshot/environments/node.ts create mode 100644 packages/vitest/src/runners.ts rename packages/vitest/src/runtime/runners/{node.ts => test.ts} (94%) create mode 100644 packages/vitest/src/runtime/setup.common.ts rename packages/vitest/src/runtime/{setup.ts => setup.node.ts} (86%) create mode 100644 test/browser/test/__snapshots__/snapshot.test.ts.snap create mode 100644 test/browser/test/snapshot.test.ts diff --git a/.eslintrc b/.eslintrc index 71bb66544a4e..ff85d08cece7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,15 +21,6 @@ } ] } - }, - { - "files": "packages/vitest/**/*.*", - "rules": { - "no-restricted-globals": [ - "error", - "performance" - ] - } } ] } diff --git a/packages/browser/src/client/main.ts b/packages/browser/src/client/main.ts index 9103fcb28a11..05ff52cc63ee 100644 --- a/packages/browser/src/client/main.ts +++ b/packages/browser/src/client/main.ts @@ -3,7 +3,8 @@ import { createClient } from '@vitest/ws-client' // eslint-disable-next-line no-restricted-imports import type { ResolvedConfig } from 'vitest' import type { VitestRunner } from '@vitest/runner' -import { BrowserTestRunner } from './runner' +import { createBrowserRunner } from './runner' +import { BrowserSnapshotEnvironment } from './snapshot' // @ts-expect-error mocking some node apis globalThis.process = { env: {}, argv: [], stdout: { write: () => {} } } @@ -19,7 +20,20 @@ let config: ResolvedConfig | undefined let runner: VitestRunner | undefined const browserHashMap = new Map() -export const client = createClient(ENTRY_URL) +export const client = createClient(ENTRY_URL, { + handlers: { + async onPathsCollected(paths) { + if (!paths) + return + // const config = __vitest_worker__.config + const now = `${new Date().getTime()}` + paths.forEach((i) => { + browserHashMap.set(i, now) + }) + await runTests(paths, config, client) + }, + }, +}) const ws = client.ws @@ -63,15 +77,25 @@ ws.addEventListener('open', async () => { await runTests(paths, config, client) }) +let hasSnapshot = false async function runTests(paths: string[], config: any, client: VitestClient) { // we use dynamic import here, because this file is bundled with UI, // but we need to resolve correct path at runtime const path = '/__vitest_index__' - const { startTests } = await import(path) as typeof import('vitest/browser') + const { startTests, setupCommonEnv, setupSnapshotEnvironment } = await import(path) as typeof import('vitest/browser') - if (!runner) - runner = new BrowserTestRunner({ config, client, browserHashMap }) + if (!runner) { + const runnerPath = '/__vitest_runners__' + const { VitestTestRunner } = await import(runnerPath) as typeof import('vitest/runners') + const BrowserRunner = createBrowserRunner(VitestTestRunner) + runner = new BrowserRunner({ config, client, browserHashMap }) + } + if (!hasSnapshot) { + setupSnapshotEnvironment(new BrowserSnapshotEnvironment(client)) + hasSnapshot = true + } + await setupCommonEnv(config) await startTests(paths, runner) await client.rpc.onFinished() diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts index 1258e03ff1c3..b3f7f93fd465 100644 --- a/packages/browser/src/client/runner.ts +++ b/packages/browser/src/client/runner.ts @@ -1,4 +1,4 @@ -import type { File, TaskResult, VitestRunner } from '@vitest/runner' +import type { File, TaskResult } from '@vitest/runner' import type { VitestClient } from '@vitest/ws-client' import type { ResolvedConfig } from '#types' @@ -8,31 +8,34 @@ interface BrowserRunnerOptions { browserHashMap: Map } -export class BrowserTestRunner implements VitestRunner { - public config: ResolvedConfig - hasMap = new Map() - client: VitestClient +export function createBrowserRunner(original: any) { + return class BrowserTestRunner extends original { + public config: ResolvedConfig + hasMap = new Map() + client: VitestClient - constructor(options: BrowserRunnerOptions) { - this.config = options.config - this.hasMap = options.browserHashMap - this.client = options.client - } + constructor(options: BrowserRunnerOptions) { + super(options.config) + this.config = options.config + this.hasMap = options.browserHashMap + this.client = options.client + } - onCollected(files: File[]): unknown { - return this.client.rpc.onCollected(files) - } + onCollected(files: File[]): unknown { + return this.client.rpc.onCollected(files) + } - onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { - return this.client.rpc.onTaskUpdate(task) - } + onTaskUpdate(task: [string, TaskResult | undefined][]): Promise { + return this.client.rpc.onTaskUpdate(task) + } - async importFile(filepath: string) { - const match = filepath.match(/^(\w:\/)/) - const hash = this.hasMap.get(filepath) - const importpath = match - ? `/@fs/${filepath.slice(match[1].length)}?v=${hash}` - : `${filepath}?v=${hash}` - await import(importpath) + async importFile(filepath: string) { + const match = filepath.match(/^(\w:\/)/) + const hash = this.hasMap.get(filepath) + const importpath = match + ? `/@fs/${filepath.slice(match[1].length)}?v=${hash}` + : `${filepath}?v=${hash}` + await import(importpath) + } } } diff --git a/packages/browser/src/client/snapshot.ts b/packages/browser/src/client/snapshot.ts new file mode 100644 index 000000000000..d7b26a666364 --- /dev/null +++ b/packages/browser/src/client/snapshot.ts @@ -0,0 +1,26 @@ +import type { VitestClient } from '@vitest/ws-client' +import type { SnapshotEnvironment } from '#types' + +export class BrowserSnapshotEnvironment implements SnapshotEnvironment { + constructor(private client: VitestClient) {} + + readSnapshotFile(filepath: string): Promise { + return this.client.rpc.readFile(filepath) + } + + saveSnapshotFile(filepath: string, snapshot: string): Promise { + return this.client.rpc.writeFile(filepath, snapshot) + } + + resolvePath(filepath: string): Promise { + return this.client.rpc.resolveSnapshotPath(filepath) + } + + removeSnapshotFile(filepath: string): Promise { + return this.client.rpc.removeFile(filepath) + } + + async prepareDirectory(filepath: string): Promise { + await this.client.rpc.createDirectory(filepath) + } +} diff --git a/packages/browser/src/client/vite.config.ts b/packages/browser/src/client/vite.config.ts index 139c3bd57a60..4c60bfe5acc0 100644 --- a/packages/browser/src/client/vite.config.ts +++ b/packages/browser/src/client/vite.config.ts @@ -8,8 +8,5 @@ export default defineConfig({ minify: false, outDir: '../../dist/client', emptyOutDir: false, - rollupOptions: { - external: ['/__vitest_index__'], - }, }, }) diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index 7c64bdc074b3..e040cbe577b7 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -38,6 +38,9 @@ export default (base = '/'): Plugin[] => { if (id === '/__vitest_index__') return this.resolve('vitest/browser') + if (id === '/__vitest_runners__') + return this.resolve('vitest/runners') + if (stubs.includes(id)) return resolve(pkgRoot, 'stubs', id) diff --git a/packages/vitest/package.json b/packages/vitest/package.json index d90914df9ecb..dbb0aaa663d7 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -49,6 +49,10 @@ "types": "./dist/browser.d.ts", "import": "./dist/browser.js" }, + "./runners": { + "types": "./dist/runners.d.ts", + "import": "./dist/runners.js" + }, "./environments": { "types": "./dist/environments.d.ts", "import": "./dist/environments.js" diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index 4188b6335fc9..d232de1d0de6 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -19,6 +19,7 @@ const entries = [ 'src/node/cli-wrapper.ts', 'src/node.ts', 'src/browser.ts', + 'src/runners.ts', 'src/environments.ts', 'src/runtime/worker.ts', 'src/runtime/loader.ts', @@ -31,6 +32,7 @@ const dtsEntries = [ 'src/node.ts', 'src/environments.ts', 'src/browser.ts', + 'src/runners.ts', 'src/config.ts', ] diff --git a/packages/vitest/runners.d.ts b/packages/vitest/runners.d.ts new file mode 100644 index 000000000000..0477c34a9a47 --- /dev/null +++ b/packages/vitest/runners.d.ts @@ -0,0 +1 @@ +export * from './dist/runners.js' diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts index 2457d76df8f0..280698ef3b18 100644 --- a/packages/vitest/src/api/setup.ts +++ b/packages/vitest/src/api/setup.ts @@ -1,4 +1,4 @@ -import { promises as fs } from 'node:fs' +import { existsSync, promises as fs } from 'node:fs' import type { BirpcReturn } from 'birpc' import { createBirpc } from 'birpc' @@ -54,9 +54,23 @@ export function setup(ctx: Vitest) { getPaths() { return ctx.state.getPaths() }, - readFile(id) { + resolveSnapshotPath(testPath) { + return ctx.snapshot.resolvePath(testPath) + }, + removeFile(id) { + return fs.unlink(id) + }, + createDirectory(id) { + return fs.mkdir(id, { recursive: true }) + }, + async readFile(id) { + if (!existsSync(id)) + return null return fs.readFile(id, 'utf-8') }, + snapshotSaved(snapshot) { + ctx.snapshot.add(snapshot) + }, writeFile(id, content) { return fs.writeFile(id, content, 'utf-8') }, diff --git a/packages/vitest/src/api/types.ts b/packages/vitest/src/api/types.ts index 9024e3a92f3e..4275d9d9ca66 100644 --- a/packages/vitest/src/api/types.ts +++ b/packages/vitest/src/api/types.ts @@ -1,5 +1,5 @@ import type { TransformResult } from 'vite' -import type { File, ModuleGraphData, Reporter, ResolvedConfig, TaskResultPack } from '../types' +import type { File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack } from '../types' export interface TransformResultWithSource extends TransformResult { source?: string @@ -13,10 +13,14 @@ export interface WebSocketHandlers { getFiles(): File[] getPaths(): string[] getConfig(): ResolvedConfig + resolveSnapshotPath(testPath: string): string getModuleGraph(id: string): Promise getTransformResult(id: string): Promise - readFile(id: string): Promise + readFile(id: string): Promise writeFile(id: string, content: string): Promise + removeFile(id: string): Promise + createDirectory(id: string): Promise + snapshotSaved(snapshot: SnapshotResult): void rerun(files: string[]): Promise updateSnapshot(file?: File): Promise } diff --git a/packages/vitest/src/browser.ts b/packages/vitest/src/browser.ts index 62fef668df77..27eabf5e6121 100644 --- a/packages/vitest/src/browser.ts +++ b/packages/vitest/src/browser.ts @@ -1 +1,3 @@ export { startTests } from '@vitest/runner' +export { setupCommonEnv } from './runtime/setup.common' +export { setupSnapshotEnvironment } from './integrations/snapshot/env' diff --git a/packages/vitest/src/index.ts b/packages/vitest/src/index.ts index 340332e2803b..b31ce92a990a 100644 --- a/packages/vitest/src/index.ts +++ b/packages/vitest/src/index.ts @@ -15,6 +15,7 @@ export { runOnce, isFirstRun } from './integrations/run-once' export * from './integrations/chai' export * from './integrations/vi' export * from './integrations/utils' +export type { SnapshotEnvironment } from './integrations/snapshot/env' export * from './types' export * from './api/types' diff --git a/packages/vitest/src/integrations/snapshot/client.ts b/packages/vitest/src/integrations/snapshot/client.ts index 5b13be0c80ba..ee5d8a46bed8 100644 --- a/packages/vitest/src/integrations/snapshot/client.ts +++ b/packages/vitest/src/integrations/snapshot/client.ts @@ -25,7 +25,6 @@ interface AssertOptions { } export class SnapshotClient { - id = Date.now() test: Test | undefined snapshotState: SnapshotState | undefined snapshotStateMap = new Map() @@ -40,9 +39,8 @@ export class SnapshotClient { if (!this.getSnapshotState(test)) { this.snapshotStateMap.set( filePath, - new SnapshotState( + await SnapshotState.create( filePath, - await rpc().resolveSnapshotPath(filePath), getWorkerState().config.snapshotOptions, ), ) diff --git a/packages/vitest/src/integrations/snapshot/env.ts b/packages/vitest/src/integrations/snapshot/env.ts new file mode 100644 index 000000000000..518a3dfb2cf8 --- /dev/null +++ b/packages/vitest/src/integrations/snapshot/env.ts @@ -0,0 +1,19 @@ +export interface SnapshotEnvironment { + resolvePath(filepath: string): Promise + prepareDirectory(filepath: string): Promise + saveSnapshotFile(filepath: string, snapshot: string): Promise + readSnapshotFile(filepath: string): Promise + removeSnapshotFile(filepath: string): Promise +} + +let _snapshotEnvironment: SnapshotEnvironment + +export function setupSnapshotEnvironment(environment: SnapshotEnvironment) { + _snapshotEnvironment = environment +} + +export function getSnapshotEnironment() { + if (!_snapshotEnvironment) + throw new Error('Snapshot environment is not setup') + return _snapshotEnvironment +} diff --git a/packages/vitest/src/integrations/snapshot/environments/node.ts b/packages/vitest/src/integrations/snapshot/environments/node.ts new file mode 100644 index 000000000000..e0b7326cb721 --- /dev/null +++ b/packages/vitest/src/integrations/snapshot/environments/node.ts @@ -0,0 +1,28 @@ +import { existsSync, promises as fs } from 'node:fs' +import { rpc } from '../../../runtime/rpc' +import type { SnapshotEnvironment } from '../env' + +export class NodeSnapshotEnvironment implements SnapshotEnvironment { + resolvePath(filepath: string): Promise { + return rpc().resolveSnapshotPath(filepath) + } + + async prepareDirectory(filepath: string): Promise { + await fs.mkdir(filepath, { recursive: true }) + } + + async saveSnapshotFile(filepath: string, snapshot: string): Promise { + await fs.writeFile(filepath, snapshot, 'utf-8') + } + + async readSnapshotFile(filepath: string): Promise { + if (!existsSync(filepath)) + return null + return fs.readFile(filepath, 'utf-8') + } + + async removeSnapshotFile(filepath: string): Promise { + if (existsSync(filepath)) + await fs.unlink(filepath) + } +} diff --git a/packages/vitest/src/integrations/snapshot/port/inlineSnapshot.ts b/packages/vitest/src/integrations/snapshot/port/inlineSnapshot.ts index d62a00bf409b..c19fde2a590b 100644 --- a/packages/vitest/src/integrations/snapshot/port/inlineSnapshot.ts +++ b/packages/vitest/src/integrations/snapshot/port/inlineSnapshot.ts @@ -1,7 +1,7 @@ -import { promises as fs } from 'fs' import type MagicString from 'magic-string' import { lineSplitRE, offsetToLineNumber, positionToOffset } from '../../../utils/source-map' import { getCallLastIndex } from '../../../utils' +import { getSnapshotEnironment } from '../env' export interface InlineSnapshot { snapshot: string @@ -13,11 +13,12 @@ export interface InlineSnapshot { export async function saveInlineSnapshots( snapshots: Array, ) { + const environment = getSnapshotEnironment() const MagicString = (await import('magic-string')).default const files = new Set(snapshots.map(i => i.file)) await Promise.all(Array.from(files).map(async (file) => { const snaps = snapshots.filter(i => i.file === file) - const code = await fs.readFile(file, 'utf8') + const code = await environment.readSnapshotFile(file) as string const s = new MagicString(code) for (const snap of snaps) { @@ -27,7 +28,7 @@ export async function saveInlineSnapshots( const transformed = s.toString() if (transformed !== code) - await fs.writeFile(file, transformed, 'utf-8') + await environment.saveSnapshotFile(file, transformed) })) } diff --git a/packages/vitest/src/integrations/snapshot/port/state.ts b/packages/vitest/src/integrations/snapshot/port/state.ts index a74563880f87..2d3596773247 100644 --- a/packages/vitest/src/integrations/snapshot/port/state.ts +++ b/packages/vitest/src/integrations/snapshot/port/state.ts @@ -5,11 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import fs from 'node:fs' import type { OptionsReceived as PrettyFormatOptions } from 'pretty-format' import type { ParsedStack, SnapshotData, SnapshotMatchOptions, SnapshotResult, SnapshotStateOptions, SnapshotUpdateState } from '../../../types' import { slash } from '../../../utils' import { parseStacktrace } from '../../../utils/source-map' +import type { SnapshotEnvironment } from '../env' +import { getSnapshotEnironment } from '../env' import type { InlineSnapshot } from './inlineSnapshot' import { saveInlineSnapshots } from './inlineSnapshot' @@ -46,6 +47,8 @@ export default class SnapshotState { private _inlineSnapshots: Array private _uncheckedKeys: Set private _snapshotFormat: PrettyFormatOptions + private _environment: SnapshotEnvironment + private _exists: boolean added: number expand: boolean @@ -53,15 +56,17 @@ export default class SnapshotState { unmatched: number updated: number - constructor( + private constructor( public testFilePath: string, public snapshotPath: string, + snapshotContent: string | null, options: SnapshotStateOptions, ) { const { data, dirty } = getSnapshotData( - this.snapshotPath, - options.updateSnapshot, + snapshotContent, + options, ) + this._exists = snapshotContent != null // TODO: update on watch? this._initialData = data this._snapshotData = data this._dirty = dirty @@ -78,6 +83,17 @@ export default class SnapshotState { printBasicPrototype: false, ...options.snapshotFormat, } + this._environment = getSnapshotEnironment() + } + + static async create( + testFilePath: string, + options: SnapshotStateOptions, + ) { + const environment = getSnapshotEnironment() + const snapshotPath = await environment.resolvePath(testFilePath) + const content = await environment.readSnapshotFile(snapshotPath) + return new SnapshotState(testFilePath, snapshotPath, content, options) } markSnapshotsAsCheckedForTest(testName: string): void { @@ -158,9 +174,11 @@ export default class SnapshotState { status.saved = true } - else if (!hasExternalSnapshots && fs.existsSync(this.snapshotPath)) { - if (this._updateSnapshot === 'all') - fs.unlinkSync(this.snapshotPath) + else if (!hasExternalSnapshots && this._exists) { + if (this._updateSnapshot === 'all') { + await this._environment.removeSnapshotFile(this.snapshotPath) + this._exists = false + } status.deleted = true } @@ -209,7 +227,7 @@ export default class SnapshotState { const expectedTrimmed = prepareExpected(expected) const pass = expectedTrimmed === prepareExpected(receivedSerialized) const hasSnapshot = expected !== undefined - const snapshotIsPersisted = isInline || fs.existsSync(this.snapshotPath) + const snapshotIsPersisted = isInline || this._exists if (pass && !isInline) { // Executing a snapshot file as JavaScript and writing the strings back diff --git a/packages/vitest/src/integrations/snapshot/port/utils.ts b/packages/vitest/src/integrations/snapshot/port/utils.ts index 598f1ea25031..d4c828727202 100644 --- a/packages/vitest/src/integrations/snapshot/port/utils.ts +++ b/packages/vitest/src/integrations/snapshot/port/utils.ts @@ -5,15 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import fs from 'node:fs' import { dirname, join } from 'pathe' import naturalCompare from 'natural-compare' import type { OptionsReceived as PrettyFormatOptions } from 'pretty-format' import { format as prettyFormat, } from 'pretty-format' -import type { SnapshotData, SnapshotUpdateState } from '../../../types' +import type { SnapshotData, SnapshotStateOptions } from '../../../types' import { isObject } from '../../../utils' +import { getSnapshotEnironment } from '../env' import { getSerializers } from './plugins' // TODO: rewrite and clean up @@ -33,19 +33,20 @@ export const keyToTestName = (key: string): string => { } export const getSnapshotData = ( - snapshotPath: string, - update: SnapshotUpdateState, + content: string | null, + options: SnapshotStateOptions, ): { data: SnapshotData dirty: boolean } => { + const update = options.updateSnapshot const data = Object.create(null) let snapshotContents = '' let dirty = false - if (fs.existsSync(snapshotPath)) { + if (content != null) { try { - snapshotContents = fs.readFileSync(snapshotPath, 'utf8') + snapshotContents = content // eslint-disable-next-line no-new-func const populate = new Function('exports', snapshotContents) populate(data) @@ -130,9 +131,10 @@ function printBacktickString(str: string): string { return `\`${escapeBacktickString(str)}\`` } -export function ensureDirectoryExists(filePath: string): void { +export async function ensureDirectoryExists(filePath: string) { try { - fs.mkdirSync(join(dirname(filePath)), { recursive: true }) + const environment = getSnapshotEnironment() + await environment.prepareDirectory(join(dirname(filePath))) } catch { } } @@ -145,6 +147,7 @@ export async function saveSnapshotFile( snapshotData: SnapshotData, snapshotPath: string, ) { + const environment = getSnapshotEnironment() const snapshots = Object.keys(snapshotData) .sort(naturalCompare) .map( @@ -152,16 +155,16 @@ export async function saveSnapshotFile( ) const content = `${writeSnapshotVersion()}\n\n${snapshots.join('\n\n')}\n` - const skipWriting = fs.existsSync(snapshotPath) && (await fs?.promises.readFile(snapshotPath, 'utf8')) === content + const oldContent = await environment.readSnapshotFile(snapshotPath) + const skipWriting = oldContent && oldContent === content if (skipWriting) return - ensureDirectoryExists(snapshotPath) - await fs?.promises.writeFile( + await ensureDirectoryExists(snapshotPath) + await environment.saveSnapshotFile( snapshotPath, content, - 'utf-8', ) } diff --git a/packages/vitest/src/runners.ts b/packages/vitest/src/runners.ts new file mode 100644 index 000000000000..ef5804e48c33 --- /dev/null +++ b/packages/vitest/src/runners.ts @@ -0,0 +1,2 @@ +export { VitestTestRunner } from './runtime/runners/test' +export { NodeBenchmarkRunner } from './runtime/runners/benchmark' diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index 268977679014..6a2f3bd9aa3c 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -7,8 +7,8 @@ import { getWorkerState, resetModules } from '../utils' import { vi } from '../integrations/vi' import { envs } from '../integrations/env' import { takeCoverageInsideWorker } from '../integrations/coverage' -import { setupGlobalEnv, withEnv } from './setup' -import { NodeTestRunner } from './runners/node' +import { setupGlobalEnv, withEnv } from './setup.node' +import { VitestTestRunner } from './runners/test' import { NodeBenchmarkRunner } from './runners/benchmark' import { rpc } from './rpc' @@ -25,7 +25,7 @@ async function getTestRunnerConstructor(config: ResolvedConfig): Promise { diff --git a/packages/vitest/src/runtime/runners/benchmark.ts b/packages/vitest/src/runtime/runners/benchmark.ts index 1761688f6538..ee411c35ac25 100644 --- a/packages/vitest/src/runtime/runners/benchmark.ts +++ b/packages/vitest/src/runtime/runners/benchmark.ts @@ -1,4 +1,3 @@ -import { performance } from 'node:perf_hooks' import type { Suite, Task, VitestRunner, VitestRunnerImportSource } from '@vitest/runner' import { updateTask as updateRunnerTask } from '@vitest/runner' import { createDefer, getSafeTimers } from '@vitest/utils' diff --git a/packages/vitest/src/runtime/runners/node.ts b/packages/vitest/src/runtime/runners/test.ts similarity index 94% rename from packages/vitest/src/runtime/runners/node.ts rename to packages/vitest/src/runtime/runners/test.ts index 20c554118d3b..99200b28bc6e 100644 --- a/packages/vitest/src/runtime/runners/node.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -1,4 +1,3 @@ -import process from 'node:process' import type { Suite, Test, TestContext, VitestRunner, VitestRunnerImportSource } from '@vitest/runner' import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect' import { getSnapshotClient } from '../../integrations/snapshot/chai' @@ -7,7 +6,7 @@ import { getFullName, getWorkerState } from '../../utils' import { createExpect } from '../../integrations/chai/index' import type { ResolvedConfig } from '#types' -export class NodeTestRunner implements VitestRunner { +export class VitestTestRunner implements VitestRunner { private snapshotClient = getSnapshotClient() private workerState = getWorkerState() @@ -28,14 +27,14 @@ export class NodeTestRunner implements VitestRunner { } onAfterRunSuite(suite: Suite) { - if (this.config.logHeapUsage) + if (this.config.logHeapUsage && typeof process !== 'undefined') suite.result!.heap = process.memoryUsage().heapUsed } onAfterRunTest(test: Test) { this.snapshotClient.clearTest() - if (this.config.logHeapUsage) + if (this.config.logHeapUsage && typeof process !== 'undefined') test.result!.heap = process.memoryUsage().heapUsed this.workerState.current = undefined diff --git a/packages/vitest/src/runtime/setup.common.ts b/packages/vitest/src/runtime/setup.common.ts new file mode 100644 index 000000000000..bc1fcb4d127c --- /dev/null +++ b/packages/vitest/src/runtime/setup.common.ts @@ -0,0 +1,23 @@ +import { setSafeTimers } from '@vitest/utils' +import { resetRunOnceCounter } from '../integrations/run-once' +import type { ResolvedConfig } from '../types' + +let globalSetup = false +export async function setupCommonEnv(config: ResolvedConfig) { + resetRunOnceCounter() + setupDefines(config.defines) + + if (globalSetup) + return + + setSafeTimers() + globalSetup = true + + if (config.globals) + (await import('../integrations/globals')).registerApiGlobally() +} + +function setupDefines(defines: Record) { + for (const key in defines) + (globalThis as any)[key] = defines[key] +} diff --git a/packages/vitest/src/runtime/setup.ts b/packages/vitest/src/runtime/setup.node.ts similarity index 86% rename from packages/vitest/src/runtime/setup.ts rename to packages/vitest/src/runtime/setup.node.ts index fb5794e0894c..41867b9d8dc4 100644 --- a/packages/vitest/src/runtime/setup.ts +++ b/packages/vitest/src/runtime/setup.node.ts @@ -2,34 +2,33 @@ import p from 'picocolors' import { installSourcemapsSupport } from 'vite-node/source-map' -import { setColors, setSafeTimers } from '@vitest/utils' +import { setColors } from '@vitest/utils' import { environments } from '../integrations/env' import type { Environment, ResolvedConfig } from '../types' -import { getSafeTimers, getWorkerState, isNode } from '../utils' +import { getSafeTimers, getWorkerState } from '../utils' import * as VitestIndex from '../index' -import { resetRunOnceCounter } from '../integrations/run-once' import { RealDate } from '../integrations/mock/date' import { expect } from '../integrations/chai' +import { setupSnapshotEnvironment } from '../integrations/snapshot/env' +import { NodeSnapshotEnvironment } from '../integrations/snapshot/environments/node' import { rpc } from './rpc' +import { setupCommonEnv } from './setup.common' +// this should only be used in Node let globalSetup = false export async function setupGlobalEnv(config: ResolvedConfig) { - resetRunOnceCounter() + await setupCommonEnv(config) Object.defineProperty(globalThis, '__vitest_index__', { value: VitestIndex, enumerable: false, }) - // should be re-declared for each test - // if run with "threads: false" - setupDefines(config.defines) - if (globalSetup) return + setupSnapshotEnvironment(new NodeSnapshotEnvironment()) setColors(p) - setSafeTimers() globalSetup = true // always mock "required" `css` files, because we cannot process them @@ -37,23 +36,13 @@ export async function setupGlobalEnv(config: ResolvedConfig) { require.extensions['.scss'] = () => ({}) require.extensions['.sass'] = () => ({}) - if (isNode) { - const state = getWorkerState() - - installSourcemapsSupport({ - getSourceMap: source => state.moduleCache.getSourceMap(source), - }) + const state = getWorkerState() - await setupConsoleLogSpy() - } - - if (config.globals) - (await import('../integrations/globals')).registerApiGlobally() -} + installSourcemapsSupport({ + getSourceMap: source => state.moduleCache.getSourceMap(source), + }) -function setupDefines(defines: Record) { - for (const key in defines) - (globalThis as any)[key] = defines[key] + await setupConsoleLogSpy() } export async function setupConsoleLogSpy() { diff --git a/test/browser/test/__snapshots__/snapshot.test.ts.snap b/test/browser/test/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 000000000000..5f969b0acbdc --- /dev/null +++ b/test/browser/test/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1 + +exports[`file snapshot 1`] = `1`; diff --git a/test/browser/test/snapshot.test.ts b/test/browser/test/snapshot.test.ts new file mode 100644 index 000000000000..ed80a694376c --- /dev/null +++ b/test/browser/test/snapshot.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from 'vitest' + +test('inline snapshot', () => { + expect(1).toMatchInlineSnapshot('1') +}) + +test('file snapshot', () => { + expect(1).toMatchSnapshot() +}) From b7c4f86fcf59d8606fddab7de6bf3a12d99fb004 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 12:35:12 +0100 Subject: [PATCH 33/47] chore: remove stubs in browser package --- packages/browser/src/node/index.ts | 16 ---------------- packages/browser/stubs/console.js | 1 - packages/browser/stubs/fs.js | 7 ------- packages/browser/stubs/local-pkg.js | 2 -- packages/browser/stubs/module.js | 1 - packages/browser/stubs/noop.js | 5 ----- packages/browser/stubs/perf_hooks.js | 1 - packages/browser/stubs/tty.js | 3 --- 8 files changed, 36 deletions(-) delete mode 100644 packages/browser/stubs/console.js delete mode 100644 packages/browser/stubs/fs.js delete mode 100644 packages/browser/stubs/local-pkg.js delete mode 100644 packages/browser/stubs/module.js delete mode 100644 packages/browser/stubs/noop.js delete mode 100644 packages/browser/stubs/perf_hooks.js delete mode 100644 packages/browser/stubs/tty.js diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index e040cbe577b7..a2cfb830bbcd 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -6,21 +6,8 @@ import { polyfillPath } from 'modern-node-polyfills' import sirv from 'sirv' import type { Plugin } from 'vite' -const stubs = [ - 'fs', - 'local-pkg', - 'module', - 'noop', - 'perf_hooks', - 'console', -] - const polyfills = [ 'util', - 'tty', - 'process', - 'path', - 'buffer', ] export default (base = '/'): Plugin[] => { @@ -41,9 +28,6 @@ export default (base = '/'): Plugin[] => { if (id === '/__vitest_runners__') return this.resolve('vitest/runners') - if (stubs.includes(id)) - return resolve(pkgRoot, 'stubs', id) - if (polyfills.includes(id)) return polyfillPath(normalizeId(id)) diff --git a/packages/browser/stubs/console.js b/packages/browser/stubs/console.js deleted file mode 100644 index a0d1b5dfa55d..000000000000 --- a/packages/browser/stubs/console.js +++ /dev/null @@ -1 +0,0 @@ -export const Console = {} diff --git a/packages/browser/stubs/fs.js b/packages/browser/stubs/fs.js deleted file mode 100644 index e34b353c6868..000000000000 --- a/packages/browser/stubs/fs.js +++ /dev/null @@ -1,7 +0,0 @@ -import noop from './noop' - -export default noop -export { noop as promises } -export function existsSync() { - return false -} diff --git a/packages/browser/stubs/local-pkg.js b/packages/browser/stubs/local-pkg.js deleted file mode 100644 index 3805f3c3bdf7..000000000000 --- a/packages/browser/stubs/local-pkg.js +++ /dev/null @@ -1,2 +0,0 @@ -export const isPackageExists = () => {} -export const importModule = () => {} diff --git a/packages/browser/stubs/module.js b/packages/browser/stubs/module.js deleted file mode 100644 index 4e4b61833884..000000000000 --- a/packages/browser/stubs/module.js +++ /dev/null @@ -1 +0,0 @@ -export const createRequire = () => {} diff --git a/packages/browser/stubs/noop.js b/packages/browser/stubs/noop.js deleted file mode 100644 index 4351d1948045..000000000000 --- a/packages/browser/stubs/noop.js +++ /dev/null @@ -1,5 +0,0 @@ -export default new Proxy({}, { - get() { - throw new Error('Module has been externalized for browser compatibility and cannot be accessed in client code.') - }, -}) diff --git a/packages/browser/stubs/perf_hooks.js b/packages/browser/stubs/perf_hooks.js deleted file mode 100644 index fc54205432ba..000000000000 --- a/packages/browser/stubs/perf_hooks.js +++ /dev/null @@ -1 +0,0 @@ -export const performance = globalThis.performance diff --git a/packages/browser/stubs/tty.js b/packages/browser/stubs/tty.js deleted file mode 100644 index b4e4d2ca539f..000000000000 --- a/packages/browser/stubs/tty.js +++ /dev/null @@ -1,3 +0,0 @@ -export const isatty = () => false - -export default { isatty } From 406db57a82771b21120fd611771f526597feaccc Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 12:56:03 +0100 Subject: [PATCH 34/47] chore: fix run --- packages/runner/package.json | 4 ++ packages/runner/rollup.config.js | 1 + packages/runner/types.d.ts | 1 + packages/ui/cypress.config.ts | 3 +- packages/vitest/LICENSE.md | 55 +------------------ packages/vitest/rollup.config.js | 6 +- packages/vitest/src/node/config.ts | 6 +- .../node/reporters/benchmark/table/index.ts | 2 +- packages/vitest/src/node/reporters/default.ts | 2 +- packages/vitest/src/node/reporters/dot.ts | 2 +- .../vitest/src/runtime/runners/benchmark.ts | 3 +- packages/vitest/src/runtime/runners/test.ts | 2 +- packages/vitest/src/runtime/setup.node.ts | 4 +- packages/vitest/src/types/tasks.ts | 2 +- 14 files changed, 27 insertions(+), 66 deletions(-) create mode 100644 packages/runner/types.d.ts diff --git a/packages/runner/package.json b/packages/runner/package.json index a15261aedcad..190a228a4d08 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -19,6 +19,10 @@ "types": "./dist/utils.d.ts", "import": "./dist/utils.js" }, + "./types": { + "types": "./dist/types.d.ts", + "import": "./dist/types.js" + }, "./*": "./*" }, "main": "./dist/index.js", diff --git a/packages/runner/rollup.config.js b/packages/runner/rollup.config.js index c0bd0c6dcbcb..a8c6d7cff5d2 100644 --- a/packages/runner/rollup.config.js +++ b/packages/runner/rollup.config.js @@ -13,6 +13,7 @@ const external = [ const entries = { index: 'src/index.ts', utils: 'src/utils/index.ts', + types: 'src/types/index.ts', } const plugins = [ diff --git a/packages/runner/types.d.ts b/packages/runner/types.d.ts new file mode 100644 index 000000000000..26a125423d41 --- /dev/null +++ b/packages/runner/types.d.ts @@ -0,0 +1 @@ +export * from './dist/types.js' diff --git a/packages/ui/cypress.config.ts b/packages/ui/cypress.config.ts index 4ed52d9d0a86..6fffdc86ede1 100644 --- a/packages/ui/cypress.config.ts +++ b/packages/ui/cypress.config.ts @@ -14,7 +14,8 @@ export default defineConfig({ }, resolve: { alias: { - '@vitest/runner/utils': resolve('../runner/src/utils/index.ts'), + '^@vitest/runner$': resolve('../runner/src/index.ts'), + '^@vitest/runner/utils$': resolve('../runner/src/utils/index.ts'), }, }, configFile: resolve('./cypress/vite.config.ts'), diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index bc9405cd664d..2c5e64c44482 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -1466,7 +1466,7 @@ Repository: sindresorhus/mimic-fn > MIT License > -> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Sindre Sorhus (sindresorhus.com) > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: > @@ -1603,7 +1603,7 @@ Repository: sindresorhus/path-key > MIT License > -> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Sindre Sorhus (sindresorhus.com) > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: > @@ -1613,57 +1613,6 @@ Repository: sindresorhus/path-key --------------------------------------- -## pathe -License: MIT -Repository: unjs/pathe - -> MIT License -> -> Copyright (c) 2021 UnJS -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. -> -> -------------------------------------------------------------------------------- -> -> Copyright Joyent, Inc. and other Node contributors. -> -> Permission is hereby granted, free of charge, to any person obtaining a -> copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to permit -> persons to whom the Software is furnished to do so, subject to the -> following conditions: -> -> The above copyright notice and this permission notice shall be included -> in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -> OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -> NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -> DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -> OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -> USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------- - ## picomatch License: MIT By: Jon Schlinkert diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index d232de1d0de6..6464db87581b 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -48,6 +48,7 @@ const external = [ 'vite-node/client', 'vite-node/server', 'vite-node/utils', + '@vitest/runner/types', ] const plugins = [ @@ -68,8 +69,11 @@ export default ({ watch }) => defineConfig([ dir: 'dist', format: 'esm', chunkFileNames: (chunkInfo) => { - const id = chunkInfo.facadeModuleId || Object.keys(chunkInfo.modules).find(i => !i.includes('node_modules') && i.includes('src/')) + let id = chunkInfo.facadeModuleId || Object.keys(chunkInfo.modules).find(i => !i.includes('node_modules') && i.includes('src/')) if (id) { + id = normalize(id) + if (id.includes(normalize('runtime/runners'))) + return 'runners-chunk.js' const parts = Array.from( new Set(relative(process.cwd(), id).split(/\//g) .map(i => i.replace(/\..*$/, '')) diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 3fa51e6a6179..5f02006a161d 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -18,11 +18,11 @@ const extraInlineDeps = [ // Vite client /vite\w*\/dist\/client\/env.mjs/, // Vitest - /\/vitest\/dist\/entry\.js/, + /\/vitest\/dist\/(runners-chunk|entry)\.js/, // yarn's .store folder - /vitest-virtual-\w+\/dist\/entry\.js/, + /vitest-virtual-\w+\/dist\/(runners-chunk|entry)\.js/, // cnpm - /@vitest\/dist\/entry\.js/, + /@vitest\/dist\/(runners-chunk|entry)\.js/, // Nuxt '@nuxt/test-utils', ] diff --git a/packages/vitest/src/node/reporters/benchmark/table/index.ts b/packages/vitest/src/node/reporters/benchmark/table/index.ts index 86d363130054..fb599bc02234 100644 --- a/packages/vitest/src/node/reporters/benchmark/table/index.ts +++ b/packages/vitest/src/node/reporters/benchmark/table/index.ts @@ -1,5 +1,5 @@ import c from 'picocolors' -import type { UserConsoleLog } from '../../../../types' +import type { UserConsoleLog } from '../../../../types/general' import { BaseReporter } from '../../base' import type { ListRendererOptions } from '../../renderers/listRenderer' import { createTableRenderer } from './tableRender' diff --git a/packages/vitest/src/node/reporters/default.ts b/packages/vitest/src/node/reporters/default.ts index a509084bfd76..e94cb8f86883 100644 --- a/packages/vitest/src/node/reporters/default.ts +++ b/packages/vitest/src/node/reporters/default.ts @@ -1,5 +1,5 @@ import c from 'picocolors' -import type { UserConsoleLog } from '../../types' +import type { UserConsoleLog } from '../../types/general' import { BaseReporter } from './base' import type { ListRendererOptions } from './renderers/listRenderer' import { createListRenderer } from './renderers/listRenderer' diff --git a/packages/vitest/src/node/reporters/dot.ts b/packages/vitest/src/node/reporters/dot.ts index d6ce952b4130..753f5d6778e7 100644 --- a/packages/vitest/src/node/reporters/dot.ts +++ b/packages/vitest/src/node/reporters/dot.ts @@ -1,4 +1,4 @@ -import type { UserConsoleLog } from '../../types' +import type { UserConsoleLog } from '../../types/general' import { BaseReporter } from './base' import { createDotRenderer } from './renderers/dotRenderer' import type { createListRenderer } from './renderers/listRenderer' diff --git a/packages/vitest/src/runtime/runners/benchmark.ts b/packages/vitest/src/runtime/runners/benchmark.ts index ee411c35ac25..a528e0625b7f 100644 --- a/packages/vitest/src/runtime/runners/benchmark.ts +++ b/packages/vitest/src/runtime/runners/benchmark.ts @@ -3,7 +3,8 @@ import { updateTask as updateRunnerTask } from '@vitest/runner' import { createDefer, getSafeTimers } from '@vitest/utils' import { getBenchFn, getBenchOptions } from '../benchmark' import { getWorkerState } from '../../utils' -import type { BenchTask, Benchmark, BenchmarkResult, ResolvedConfig } from '#types' +import type { BenchTask, Benchmark, BenchmarkResult } from '../../types/benchmark' +import type { ResolvedConfig } from '../../types/config' async function importTinybench() { if (!globalThis.EventTarget) diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index 99200b28bc6e..ca41f1f45970 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -4,7 +4,7 @@ import { getSnapshotClient } from '../../integrations/snapshot/chai' import { vi } from '../../integrations/vi' import { getFullName, getWorkerState } from '../../utils' import { createExpect } from '../../integrations/chai/index' -import type { ResolvedConfig } from '#types' +import type { ResolvedConfig } from '../../types/config' export class VitestTestRunner implements VitestRunner { private snapshotClient = getSnapshotClient() diff --git a/packages/vitest/src/runtime/setup.node.ts b/packages/vitest/src/runtime/setup.node.ts index 41867b9d8dc4..a76c8c49547a 100644 --- a/packages/vitest/src/runtime/setup.node.ts +++ b/packages/vitest/src/runtime/setup.node.ts @@ -1,5 +1,4 @@ -/* eslint-disable n/no-deprecated-api */ - +import { createRequire } from 'node:module' import p from 'picocolors' import { installSourcemapsSupport } from 'vite-node/source-map' import { setColors } from '@vitest/utils' @@ -31,6 +30,7 @@ export async function setupGlobalEnv(config: ResolvedConfig) { setColors(p) globalSetup = true + const require = createRequire(import.meta.url) // always mock "required" `css` files, because we cannot process them require.extensions['.css'] = () => ({}) require.extensions['.scss'] = () => ({}) diff --git a/packages/vitest/src/types/tasks.ts b/packages/vitest/src/types/tasks.ts index a078e109da52..d1fcdc66232e 100644 --- a/packages/vitest/src/types/tasks.ts +++ b/packages/vitest/src/types/tasks.ts @@ -21,4 +21,4 @@ export type { RuntimeContext, TestContext, OnTestFailedHandler, -} from '@vitest/runner' +} from '@vitest/runner/types' From 72f1592dc6c7e7af2aa84338e670d1c08a8a0ccd Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 13:07:23 +0100 Subject: [PATCH 35/47] chore: fix cypress tests --- packages/ui/cypress.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/cypress.config.ts b/packages/ui/cypress.config.ts index 6fffdc86ede1..c95ce2dd80e7 100644 --- a/packages/ui/cypress.config.ts +++ b/packages/ui/cypress.config.ts @@ -13,10 +13,10 @@ export default defineConfig({ 'process.env.NODE_DEBUG': '"false"', }, resolve: { - alias: { - '^@vitest/runner$': resolve('../runner/src/index.ts'), - '^@vitest/runner/utils$': resolve('../runner/src/utils/index.ts'), - }, + alias: [ + { find: /^\@vitest\/utils/, replacement: resolve('../utils/src/') }, + { find: /^\@vitest\/runner/, replacement: resolve('../runner/src/') }, + ], }, configFile: resolve('./cypress/vite.config.ts'), }, From c6bae839bbfd9d80728a95fd683aed71b11f6103 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 21 Jan 2023 13:14:26 +0100 Subject: [PATCH 36/47] chore: normalize --- packages/vitest/rollup.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index 6464db87581b..ca6bcf66a04e 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -69,10 +69,10 @@ export default ({ watch }) => defineConfig([ dir: 'dist', format: 'esm', chunkFileNames: (chunkInfo) => { - let id = chunkInfo.facadeModuleId || Object.keys(chunkInfo.modules).find(i => !i.includes('node_modules') && i.includes('src/')) + let id = chunkInfo.facadeModuleId || Object.keys(chunkInfo.modules).find(i => !i.includes('node_modules') && (i.includes('src/') || i.includes('src\\'))) if (id) { id = normalize(id) - if (id.includes(normalize('runtime/runners'))) + if (id.includes('runtime/runners')) return 'runners-chunk.js' const parts = Array.from( new Set(relative(process.cwd(), id).split(/\//g) From 82e923385b74ea5d19953c75001b0e5c14c1a16c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 13:24:13 +0100 Subject: [PATCH 37/47] chore: add pathe to collect --- packages/runner/package.json | 3 ++- packages/runner/src/collect.ts | 4 ++-- packages/runner/src/setup.ts | 2 -- pnpm-lock.yaml | 2 ++ 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/runner/package.json b/packages/runner/package.json index 190a228a4d08..ea277ed7877d 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@vitest/utils": "workspace:*", - "p-limit": "^4.0.0" + "p-limit": "^4.0.0", + "pathe": "^1.1.0" } } diff --git a/packages/runner/src/collect.ts b/packages/runner/src/collect.ts index 71fee3d41558..ea2ea2633d2c 100644 --- a/packages/runner/src/collect.ts +++ b/packages/runner/src/collect.ts @@ -1,3 +1,4 @@ +import { relative } from 'pathe' import type { File } from './types' import type { VitestRunner } from './types/runner' import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from './utils/collect' @@ -15,8 +16,7 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi const config = runner.config for (const filepath of paths) { - // TODO /full/path/to/file.js -> /to/file - const path = filepath.slice(config.root.length + 1) + const path = relative(config.root, filepath) const file: File = { id: generateHash(path), name: path, diff --git a/packages/runner/src/setup.ts b/packages/runner/src/setup.ts index ee430eeca2e7..750a57a18c4d 100644 --- a/packages/runner/src/setup.ts +++ b/packages/runner/src/setup.ts @@ -5,8 +5,6 @@ export async function runSetupFiles(config: VitestRunnerConfig, runner: VitestRu const files = toArray(config.setupFiles) await Promise.all( files.map(async (fsPath) => { - // TODO: check if it's a setup file and remove - // getWorkerState().moduleCache.delete(fsPath) await runner.importFile(fsPath, 'setup') }), ) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3ffdbde7696..efacc9fcf190 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -709,9 +709,11 @@ importers: specifiers: '@vitest/utils': workspace:* p-limit: ^4.0.0 + pathe: ^1.1.0 dependencies: '@vitest/utils': link:../utils p-limit: 4.0.0 + pathe: 1.1.0 packages/spy: specifiers: From d0b793cc8ff54729af53a7caaec0d51e997fe5ad Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 14:12:40 +0100 Subject: [PATCH 38/47] chore: fix bench --- bench/package.json | 11 +- bench/pnpm-lock.yaml | 1026 +++++++++++++++-- bench/src/bench.ts | 8 +- bench/test/vue/jest.config.js | 1 + bench/test/vue/setup.jest.js | 3 + packages/vitest/src/runtime/benchmark.ts | 4 + .../vitest/src/runtime/runners/benchmark.ts | 4 + 7 files changed, 952 insertions(+), 105 deletions(-) create mode 100644 bench/test/vue/setup.jest.js diff --git a/bench/package.json b/bench/package.json index 396fffc382cc..8cdfabe0dd79 100644 --- a/bench/package.json +++ b/bench/package.json @@ -9,14 +9,23 @@ "@actions/core": "^1.10.0", "@actions/exec": "^1.1.1", "@actions/github": "^5.1.1", + "@babel/preset-env": "^7.18.2", + "@babel/preset-typescript": "^7.17.12", "@happy-dom/jest-environment": "^8.1.3", "@types/benchmark": "^2.1.2", + "@vitejs/plugin-vue": "^4.0.0", + "@vue/test-utils": "^2.2.7", + "@vue/vue3-jest": "^27.0.0", + "babel-jest": "^27.5.1", "benchmark": "^2.1.4", "esno": "^0.16.3", "execa": "^6.1.0", "fs-extra": "^11.1.0", + "jest": "^27.5.1", "markdown-table": "^3.0.3", "microtime": "^3.1.1", - "vitest": "link:../packages/vitest" + "ts-jest": "^27.1.5", + "vitest": "link:../packages/vitest", + "vue": "^3.2.45" } } diff --git a/bench/pnpm-lock.yaml b/bench/pnpm-lock.yaml index e51e222f9357..c414ca2efa3c 100644 --- a/bench/pnpm-lock.yaml +++ b/bench/pnpm-lock.yaml @@ -7,28 +7,46 @@ importers: '@actions/core': ^1.10.0 '@actions/exec': ^1.1.1 '@actions/github': ^5.1.1 + '@babel/preset-env': ^7.18.2 + '@babel/preset-typescript': ^7.17.12 '@happy-dom/jest-environment': ^8.1.3 '@types/benchmark': ^2.1.2 + '@vitejs/plugin-vue': ^4.0.0 + '@vue/test-utils': ^2.2.7 + '@vue/vue3-jest': ^27.0.0 + babel-jest: ^27.5.1 benchmark: ^2.1.4 esno: ^0.16.3 execa: ^6.1.0 fs-extra: ^11.1.0 + jest: ^27.5.1 markdown-table: ^3.0.3 microtime: ^3.1.1 + ts-jest: ^27.1.5 vitest: link:../packages/vitest + vue: ^3.2.45 devDependencies: '@actions/core': 1.10.0 '@actions/exec': 1.1.1 '@actions/github': 5.1.1 + '@babel/preset-env': 7.18.2_@babel+core@7.20.5 + '@babel/preset-typescript': 7.17.12_@babel+core@7.20.5 '@happy-dom/jest-environment': 8.1.3 '@types/benchmark': 2.1.2 + '@vitejs/plugin-vue': 4.0.0_vite@4.0.0+vue@3.2.45 + '@vue/test-utils': 2.2.7_vue@3.2.45 + '@vue/vue3-jest': 27.0.0_rggaoj2gxgtz66f7k5b6ywrwv4 + babel-jest: 27.5.1_@babel+core@7.20.5 benchmark: 2.1.4 esno: 0.16.3 execa: 6.1.0 fs-extra: 11.1.0 + jest: 27.5.1 markdown-table: 3.0.3 microtime: 3.1.1 + ts-jest: 27.1.5_lymxezh3rizc3sf4nv2timwyqu vitest: link:../packages/vitest + vue: 3.2.45 ../packages/browser: specifiers: @@ -517,28 +535,6 @@ packages: - supports-color dev: true - /@babel/core/7.19.3: - resolution: {integrity: sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.20.5 - '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.19.3 - '@babel/helper-module-transforms': 7.20.2 - '@babel/helpers': 7.20.6 - '@babel/parser': 7.20.5 - '@babel/template': 7.18.10 - '@babel/traverse': 7.20.5 - '@babel/types': 7.20.5 - convert-source-map: 1.8.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - /@babel/core/7.20.5: resolution: {integrity: sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==} engines: {node: '>=6.9.0'} @@ -560,7 +556,6 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color - dev: true /@babel/generator/7.18.2: resolution: {integrity: sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==} @@ -627,18 +622,6 @@ packages: semver: 6.3.0 dev: true - /@babel/helper-compilation-targets/7.20.0_@babel+core@7.19.3: - resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.20.5 - '@babel/core': 7.19.3 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.4 - semver: 6.3.0 - /@babel/helper-compilation-targets/7.20.0_@babel+core@7.20.5: resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} engines: {node: '>=6.9.0'} @@ -650,7 +633,6 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 semver: 6.3.0 - dev: true /@babel/helper-create-class-features-plugin/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==} @@ -717,6 +699,17 @@ packages: regexpu-core: 5.0.1 dev: true + /@babel/helper-create-regexp-features-plugin/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + regexpu-core: 5.0.1 + dev: true + /@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.18.2: resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==} peerDependencies: @@ -735,6 +728,24 @@ packages: - supports-color dev: true + /@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.20.5: + resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/traverse': 7.20.5 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-environment-visitor/7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} @@ -986,13 +997,6 @@ packages: '@babel/types': 7.20.5 dev: true - /@babel/parser/7.19.3: - resolution: {integrity: sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.20.5 - /@babel/parser/7.20.5: resolution: {integrity: sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==} engines: {node: '>=6.0.0'} @@ -1010,6 +1014,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==} engines: {node: '>=6.9.0'} @@ -1022,6 +1036,18 @@ packages: '@babel/plugin-proposal-optional-chaining': 7.17.12_@babel+core@7.18.2 dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + '@babel/plugin-proposal-optional-chaining': 7.17.12_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-async-generator-functions/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==} engines: {node: '>=6.9.0'} @@ -1036,6 +1062,20 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-async-generator-functions/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.16.8 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-class-properties/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==} engines: {node: '>=6.9.0'} @@ -1049,6 +1089,19 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-class-properties/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-class-static-block/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==} engines: {node: '>=6.9.0'} @@ -1063,6 +1116,20 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-class-static-block/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==} engines: {node: '>=6.9.0'} @@ -1074,6 +1141,17 @@ packages: '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-export-namespace-from/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==} engines: {node: '>=6.9.0'} @@ -1085,6 +1163,17 @@ packages: '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-export-namespace-from/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-json-strings/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==} engines: {node: '>=6.9.0'} @@ -1096,6 +1185,17 @@ packages: '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-json-strings/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-logical-assignment-operators/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==} engines: {node: '>=6.9.0'} @@ -1107,6 +1207,17 @@ packages: '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-logical-assignment-operators/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-nullish-coalescing-operator/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==} engines: {node: '>=6.9.0'} @@ -1118,6 +1229,17 @@ packages: '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-nullish-coalescing-operator/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==} engines: {node: '>=6.9.0'} @@ -1129,6 +1251,17 @@ packages: '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-object-rest-spread/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==} engines: {node: '>=6.9.0'} @@ -1143,6 +1276,20 @@ packages: '@babel/plugin-transform-parameters': 7.17.12_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-object-rest-spread/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.5 + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-transform-parameters': 7.17.12_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-optional-catch-binding/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==} engines: {node: '>=6.9.0'} @@ -1154,6 +1301,17 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-optional-catch-binding/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-optional-chaining/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==} engines: {node: '>=6.9.0'} @@ -1166,6 +1324,18 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.18.2 dev: true + /@babel/plugin-proposal-optional-chaining/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.5 + dev: true + /@babel/plugin-proposal-private-methods/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==} engines: {node: '>=6.9.0'} @@ -1179,6 +1349,19 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-private-methods/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-private-property-in-object/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==} engines: {node: '>=6.9.0'} @@ -1194,6 +1377,21 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-private-property-in-object/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-unicode-property-regex/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==} engines: {node: '>=4'} @@ -1205,16 +1403,27 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.18.2: - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + /@babel/plugin-proposal-unicode-property-regex/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==} + engines: {node: '>=4'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.2 + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.17.12_@babel+core@7.20.5 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.5: + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.18.2: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.2 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.5: resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1269,6 +1478,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.20.5: + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.18.2: resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: @@ -1278,6 +1497,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.18.2: resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} peerDependencies: @@ -1287,6 +1515,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-import-assertions/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==} engines: {node: '>=6.9.0'} @@ -1297,6 +1534,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-import-assertions/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.18.2: resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -1461,6 +1708,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.20.5: + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.18.2: resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -1511,6 +1768,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-arrow-functions/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-async-to-generator/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==} engines: {node: '>=6.9.0'} @@ -1525,6 +1792,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-async-to-generator/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.16.8 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-block-scoped-functions/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==} engines: {node: '>=6.9.0'} @@ -1535,6 +1816,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-block-scoped-functions/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-block-scoping/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-jw8XW/B1i7Lqwqj2CbrViPcZijSxfguBWZP2aN59NHgxUyO/OcO1mfdCxH13QhN5LbWhPkX+f+brKGhZTiqtZQ==} engines: {node: '>=6.9.0'} @@ -1545,6 +1836,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-block-scoping/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-jw8XW/B1i7Lqwqj2CbrViPcZijSxfguBWZP2aN59NHgxUyO/OcO1mfdCxH13QhN5LbWhPkX+f+brKGhZTiqtZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-classes/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-cvO7lc7pZat6BsvH6l/EGaI8zpl8paICaoGk+7x7guvtfak/TbIf66nYmJOH13EuG0H+Xx3M+9LQDtSvZFKXKw==} engines: {node: '>=6.9.0'} @@ -1564,6 +1865,25 @@ packages: - supports-color dev: true + /@babel/plugin-transform-classes/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-cvO7lc7pZat6BsvH6l/EGaI8zpl8paICaoGk+7x7guvtfak/TbIf66nYmJOH13EuG0H+Xx3M+9LQDtSvZFKXKw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.19.1 + '@babel/helper-split-export-declaration': 7.18.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-computed-properties/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==} engines: {node: '>=6.9.0'} @@ -1574,6 +1894,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-computed-properties/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-destructuring/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==} engines: {node: '>=6.9.0'} @@ -1584,6 +1914,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-destructuring/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-dotall-regex/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==} engines: {node: '>=6.9.0'} @@ -1595,6 +1935,17 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-dotall-regex/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.17.12_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-duplicate-keys/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==} engines: {node: '>=6.9.0'} @@ -1605,6 +1956,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-duplicate-keys/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-exponentiation-operator/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==} engines: {node: '>=6.9.0'} @@ -1616,6 +1977,17 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-exponentiation-operator/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.16.7 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-for-of/7.18.1_@babel+core@7.18.2: resolution: {integrity: sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==} engines: {node: '>=6.9.0'} @@ -1626,6 +1998,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-for-of/7.18.1_@babel+core@7.20.5: + resolution: {integrity: sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-function-name/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==} engines: {node: '>=6.9.0'} @@ -1638,6 +2020,18 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-function-name/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-literals/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==} engines: {node: '>=6.9.0'} @@ -1648,6 +2042,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-literals/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-member-expression-literals/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==} engines: {node: '>=6.9.0'} @@ -1658,6 +2062,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-member-expression-literals/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-modules-amd/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==} engines: {node: '>=6.9.0'} @@ -1672,6 +2086,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-amd/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs/7.17.7_@babel+core@7.18.2: resolution: {integrity: sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA==} engines: {node: '>=6.9.0'} @@ -1702,6 +2130,21 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-commonjs/7.18.2_@babel+core@7.20.5: + resolution: {integrity: sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-simple-access': 7.20.2 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-systemjs/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-vwKpxdHnlM5tIrRt/eA0bzfbi7gUBLN08vLu38np1nZevlPySRe6yvuATJB5F/WPJ+ur4OXwpVYq9+BsxqAQuQ==} engines: {node: '>=6.9.0'} @@ -1718,65 +2161,180 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-systemjs/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-vwKpxdHnlM5tIrRt/eA0bzfbi7gUBLN08vLu38np1nZevlPySRe6yvuATJB5F/WPJ+ur4OXwpVYq9+BsxqAQuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-identifier': 7.19.1 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-umd/7.18.0_@babel+core@7.18.2: resolution: {integrity: sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.2 - '@babel/helper-module-transforms': 7.20.2 + '@babel/core': 7.18.2 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-umd/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex/7.17.12_@babel+core@7.18.2: + resolution: {integrity: sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.2 + '@babel/helper-create-regexp-features-plugin': 7.17.12_@babel+core@7.18.2 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.17.12_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-new-target/7.17.12_@babel+core@7.18.2: + resolution: {integrity: sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.2 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-new-target/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-object-super/7.16.7_@babel+core@7.18.2: + resolution: {integrity: sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.2 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-object-super/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-parameters/7.17.12_@babel+core@7.18.2: + resolution: {integrity: sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.2 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-parameters/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 '@babel/helper-plugin-utils': 7.20.2 - transitivePeerDependencies: - - supports-color dev: true - /@babel/plugin-transform-named-capturing-groups-regex/7.17.12_@babel+core@7.18.2: - resolution: {integrity: sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==} + /@babel/plugin-transform-property-literals/7.16.7_@babel+core@7.18.2: + resolution: {integrity: sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.2 - '@babel/helper-create-regexp-features-plugin': 7.17.12_@babel+core@7.18.2 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-new-target/7.17.12_@babel+core@7.18.2: - resolution: {integrity: sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==} + /@babel/plugin-transform-property-literals/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.2 + '@babel/core': 7.20.5 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-object-super/7.16.7_@babel+core@7.18.2: - resolution: {integrity: sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==} + /@babel/plugin-transform-regenerator/7.18.0_@babel+core@7.18.2: + resolution: {integrity: sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.2 '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-replace-supers': 7.19.1 - transitivePeerDependencies: - - supports-color + regenerator-transform: 0.15.0 dev: true - /@babel/plugin-transform-parameters/7.17.12_@babel+core@7.18.2: - resolution: {integrity: sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==} + /@babel/plugin-transform-regenerator/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.2 + '@babel/core': 7.20.5 '@babel/helper-plugin-utils': 7.20.2 + regenerator-transform: 0.15.0 dev: true - /@babel/plugin-transform-property-literals/7.16.7_@babel+core@7.18.2: - resolution: {integrity: sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==} + /@babel/plugin-transform-reserved-words/7.17.12_@babel+core@7.18.2: + resolution: {integrity: sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1785,19 +2343,18 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-regenerator/7.18.0_@babel+core@7.18.2: - resolution: {integrity: sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==} + /@babel/plugin-transform-reserved-words/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.2 + '@babel/core': 7.20.5 '@babel/helper-plugin-utils': 7.20.2 - regenerator-transform: 0.15.0 dev: true - /@babel/plugin-transform-reserved-words/7.17.12_@babel+core@7.18.2: - resolution: {integrity: sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==} + /@babel/plugin-transform-shorthand-properties/7.16.7_@babel+core@7.18.2: + resolution: {integrity: sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1806,13 +2363,13 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-shorthand-properties/7.16.7_@babel+core@7.18.2: + /@babel/plugin-transform-shorthand-properties/7.16.7_@babel+core@7.20.5: resolution: {integrity: sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.2 + '@babel/core': 7.20.5 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -1827,6 +2384,17 @@ packages: '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 dev: true + /@babel/plugin-transform-spread/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + dev: true + /@babel/plugin-transform-sticky-regex/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==} engines: {node: '>=6.9.0'} @@ -1837,6 +2405,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-sticky-regex/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-template-literals/7.18.2_@babel+core@7.18.2: resolution: {integrity: sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==} engines: {node: '>=6.9.0'} @@ -1847,6 +2425,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-template-literals/7.18.2_@babel+core@7.20.5: + resolution: {integrity: sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-typeof-symbol/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==} engines: {node: '>=6.9.0'} @@ -1857,6 +2445,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-typeof-symbol/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-typescript/7.18.4_@babel+core@7.18.2: resolution: {integrity: sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw==} engines: {node: '>=6.9.0'} @@ -1895,6 +2493,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-unicode-escapes/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-unicode-regex/7.16.7_@babel+core@7.18.2: resolution: {integrity: sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==} engines: {node: '>=6.9.0'} @@ -1906,6 +2514,17 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-unicode-regex/7.16.7_@babel+core@7.20.5: + resolution: {integrity: sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.17.12_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/preset-env/7.18.2_@babel+core@7.18.2: resolution: {integrity: sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==} engines: {node: '>=6.9.0'} @@ -1992,6 +2611,92 @@ packages: - supports-color dev: true + /@babel/preset-env/7.18.2_@babel+core@7.20.5: + resolution: {integrity: sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.5 + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-async-generator-functions': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-class-properties': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-class-static-block': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-proposal-dynamic-import': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-proposal-export-namespace-from': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-json-strings': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-logical-assignment-operators': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-numeric-separator': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-proposal-object-rest-spread': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-proposal-optional-catch-binding': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-proposal-optional-chaining': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-private-methods': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-private-property-in-object': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-proposal-unicode-property-regex': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.5 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.20.5 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-import-assertions': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.5 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.20.5 + '@babel/plugin-transform-arrow-functions': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-async-to-generator': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-block-scoped-functions': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-block-scoping': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-classes': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-computed-properties': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-destructuring': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-transform-dotall-regex': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-duplicate-keys': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-exponentiation-operator': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-for-of': 7.18.1_@babel+core@7.20.5 + '@babel/plugin-transform-function-name': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-literals': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-member-expression-literals': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-modules-amd': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-transform-modules-commonjs': 7.18.2_@babel+core@7.20.5 + '@babel/plugin-transform-modules-systemjs': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-transform-modules-umd': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-transform-named-capturing-groups-regex': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-new-target': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-object-super': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-parameters': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-property-literals': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-regenerator': 7.18.0_@babel+core@7.20.5 + '@babel/plugin-transform-reserved-words': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-shorthand-properties': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-spread': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-sticky-regex': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-template-literals': 7.18.2_@babel+core@7.20.5 + '@babel/plugin-transform-typeof-symbol': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-unicode-escapes': 7.16.7_@babel+core@7.20.5 + '@babel/plugin-transform-unicode-regex': 7.16.7_@babel+core@7.20.5 + '@babel/preset-modules': 0.1.5_@babel+core@7.20.5 + '@babel/types': 7.20.5 + babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.20.5 + babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.20.5 + babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.20.5 + core-js-compat: 3.22.7 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/preset-modules/0.1.5_@babel+core@7.18.2: resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} peerDependencies: @@ -2005,6 +2710,19 @@ packages: esutils: 2.0.3 dev: true + /@babel/preset-modules/0.1.5_@babel+core@7.20.5: + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-proposal-unicode-property-regex': 7.17.12_@babel+core@7.20.5 + '@babel/plugin-transform-dotall-regex': 7.16.7_@babel+core@7.20.5 + '@babel/types': 7.20.5 + esutils: 2.0.3 + dev: true + /@babel/preset-typescript/7.17.12_@babel+core@7.18.2: resolution: {integrity: sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==} engines: {node: '>=6.9.0'} @@ -2019,6 +2737,20 @@ packages: - supports-color dev: true + /@babel/preset-typescript/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-transform-typescript': 7.20.2_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/runtime/7.16.7: resolution: {integrity: sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==} engines: {node: '>=6.9.0'} @@ -2575,7 +3307,7 @@ packages: istanbul-lib-instrument: 5.2.1 istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.3 + istanbul-reports: 3.1.5 jest-haste-map: 27.5.1 jest-resolve: 27.5.1 jest-util: 27.5.1 @@ -3477,6 +4209,37 @@ packages: - supports-color dev: true + /@vue/vue3-jest/27.0.0_rggaoj2gxgtz66f7k5b6ywrwv4: + resolution: {integrity: sha512-VL61CgZBoQqayXfzlZJHHpZuX4lsT8dmdZMJzADhdAJjKu26JBpypHr/2ppevxItljPiuALQW4MKhhCXZRXnLg==} + peerDependencies: + '@babel/core': 7.x + babel-jest: 27.x + jest: 27.x + ts-jest: 27.x + typescript: '>= 3.x' + vue: ^3.0.0-0 + peerDependenciesMeta: + ts-jest: + optional: true + typescript: + optional: true + dependencies: + '@babel/core': 7.20.5 + '@babel/plugin-transform-modules-commonjs': 7.18.2_@babel+core@7.20.5 + babel-jest: 27.5.1_@babel+core@7.20.5 + chalk: 2.4.2 + convert-source-map: 1.8.0 + css-tree: 2.3.1 + jest: 27.5.1 + source-map: 0.5.6 + ts-jest: 27.1.5_lymxezh3rizc3sf4nv2timwyqu + tsconfig: 7.0.0 + typescript: 4.9.4 + vue: 3.2.45 + transitivePeerDependencies: + - supports-color + dev: true + /@vueuse/core/9.10.0_vue@3.2.45: resolution: {integrity: sha512-CxMewME07qeuzuT/AOIQGv0EhhDoojniqU6pC3F8m5VC76L47UT18DcX88kWlP3I7d3qMJ4u/PD8iSRsy3bmNA==} dependencies: @@ -3771,6 +4534,19 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs2/0.3.0_@babel+core@7.20.5: + resolution: {integrity: sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.5 + '@babel/core': 7.20.5 + '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.20.5 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs3/0.5.2_@babel+core@7.18.2: resolution: {integrity: sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==} peerDependencies: @@ -3783,6 +4559,18 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs3/0.5.2_@babel+core@7.20.5: + resolution: {integrity: sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.20.5 + core-js-compat: 3.22.7 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-regenerator/0.3.0_@babel+core@7.18.2: resolution: {integrity: sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==} peerDependencies: @@ -3794,6 +4582,17 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-regenerator/0.3.0_@babel+core@7.20.5: + resolution: {integrity: sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /babel-preset-current-node-syntax/1.0.1_@babel+core@7.18.2: resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: @@ -4141,7 +4940,7 @@ packages: wrap-ansi: 7.0.0 /co/4.6.0: - resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} dev: true @@ -4486,7 +5285,7 @@ packages: dev: true /dedent/0.7.0: - resolution: {integrity: sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=} + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true /deep-eql/4.1.3: @@ -5216,7 +6015,7 @@ packages: dev: true /exit/0.1.2: - resolution: {integrity: sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=} + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} dev: true @@ -5705,7 +6504,7 @@ packages: dev: true /imurmurhash/0.1.4: - resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} dev: true @@ -5737,7 +6536,7 @@ packages: dev: true /is-arrayish/0.2.1: - resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true /is-bigint/1.0.4: @@ -5931,8 +6730,8 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.19.3 - '@babel/parser': 7.19.3 + '@babel/core': 7.20.5 + '@babel/parser': 7.20.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.0 @@ -5957,21 +6756,12 @@ packages: transitivePeerDependencies: - supports-color - /istanbul-reports/3.1.3: - resolution: {integrity: sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==} - engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.0 - dev: true - /istanbul-reports/3.1.5: resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} engines: {node: '>=8'} dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.0 - dev: false /jest-changed-files/27.5.1: resolution: {integrity: sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==} @@ -6903,7 +7693,7 @@ packages: hasBin: true /natural-compare/1.4.0: - resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true /node-addon-api/5.0.0: @@ -6932,7 +7722,7 @@ packages: dev: true /node-int64/0.4.0: - resolution: {integrity: sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=} + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true /node-releases/2.0.6: @@ -7327,7 +8117,7 @@ packages: dev: true /require-directory/2.1.1: - resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} /requires-port/1.0.0: @@ -7552,7 +8342,7 @@ packages: source-map: 0.6.1 /source-map/0.5.6: - resolution: {integrity: sha1-dc449SvwczxafwwRjYEzSiu19BI=} + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} engines: {node: '>=0.10.0'} dev: true @@ -7574,7 +8364,7 @@ packages: dev: true /sprintf-js/1.0.3: - resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true /sshpk/1.17.0: @@ -7642,7 +8432,7 @@ packages: ansi-regex: 6.0.1 /strip-bom/3.0.0: - resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} dev: true @@ -7662,7 +8452,7 @@ packages: dev: true /strip-json-comments/2.0.1: - resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} dev: true @@ -7853,6 +8643,41 @@ packages: yargs-parser: 20.2.9 dev: true + /ts-jest/27.1.5_lymxezh3rizc3sf4nv2timwyqu: + resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@types/jest': ^27.0.0 + babel-jest: '>=27.0.0 <28' + esbuild: '*' + jest: ^27.0.0 + typescript: '>=3.8 <5.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@types/jest': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.20.5 + babel-jest: 27.5.1_@babel+core@7.20.5 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 27.5.1 + jest-util: 27.5.1 + json5: 2.2.1 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.5 + typescript: 4.9.4 + yargs-parser: 20.2.9 + dev: true + /tsconfig/7.0.0: resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} dependencies: @@ -8314,6 +9139,7 @@ packages: /w3c-hr-time/1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} + deprecated: Use your platform's native performance.now() and performance.timeOrigin. dependencies: browser-process-hrtime: 1.0.0 dev: true diff --git a/bench/src/bench.ts b/bench/src/bench.ts index ea99804900a5..0fec41613888 100644 --- a/bench/src/bench.ts +++ b/bench/src/bench.ts @@ -72,7 +72,7 @@ export function runBench(callback: (data: Result[]) => void) { defer: true, fn: (deferred: Deferred) => execa('pnpm', ['test:vitest'], execaOptions) .on('exit', (code) => { - if (code > 0) + if (code) exit(suite, code) else deferred.resolve() @@ -83,7 +83,7 @@ export function runBench(callback: (data: Result[]) => void) { defer: true, fn: (deferred: Deferred) => execa('pnpm', ['test:jest'], execaOptions) .on('exit', (code) => { - if (code > 0) + if (code) exit(suite, code) else deferred.resolve() @@ -94,8 +94,8 @@ export function runBench(callback: (data: Result[]) => void) { bench.on('complete', () => { const results = bench .map((run: Target): Result => ({ - name: run.name, - ...run.stats, + name: run.name!, + ...run.stats!, })) .sort((a, b) => { return a.mean - b.mean }) diff --git a/bench/test/vue/jest.config.js b/bench/test/vue/jest.config.js index 81d292924c2a..e00d4e0c0c5c 100644 --- a/bench/test/vue/jest.config.js +++ b/bench/test/vue/jest.config.js @@ -5,6 +5,7 @@ module.exports = { 'ts', 'vue', ], + setupFiles: ['./setup.jest.js'], transform: { '.*\\.(vue)$': '@vue/vue3-jest', '.*\\.(ts)$': 'babel-jest', diff --git a/bench/test/vue/setup.jest.js b/bench/test/vue/setup.jest.js new file mode 100644 index 000000000000..ace9bcf73358 --- /dev/null +++ b/bench/test/vue/setup.jest.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line no-undef +globalThis.vi = jest +globalThis.vi.dynamicImportSettled = () => Promise.resolve() diff --git a/packages/vitest/src/runtime/benchmark.ts b/packages/vitest/src/runtime/benchmark.ts index 5196a156046f..0e25180e474d 100644 --- a/packages/vitest/src/runtime/benchmark.ts +++ b/packages/vitest/src/runtime/benchmark.ts @@ -3,6 +3,7 @@ import { getCurrentSuite } from '@vitest/runner' import { createChainable } from '@vitest/runner/utils' import { noop } from '@vitest/utils' import type { BenchFunction, BenchOptions, BenchmarkAPI } from '../types' +import { isRunningInBenchmark } from '../utils' const benchFns = new WeakMap() const benchOptsMap = new WeakMap() @@ -17,6 +18,9 @@ export function getBenchFn(key: TaskCustom): BenchFunction { export const bench = createBenchmark( function (name, fn: BenchFunction = noop, options: BenchOptions = {}) { + if (!isRunningInBenchmark()) + throw new Error('`bench()` is only available in benchmark mode.') + const task = getCurrentSuite().custom.call(this, name) task.meta = { benchmark: true, diff --git a/packages/vitest/src/runtime/runners/benchmark.ts b/packages/vitest/src/runtime/runners/benchmark.ts index a528e0625b7f..9dfe2a8d74af 100644 --- a/packages/vitest/src/runtime/runners/benchmark.ts +++ b/packages/vitest/src/runtime/runners/benchmark.ts @@ -132,4 +132,8 @@ export class NodeBenchmarkRunner implements VitestRunner { async runSuite(suite: Suite): Promise { await runBenchmarkSuite(suite, this) } + + async runTest(): Promise { + throw new Error('`test()` and `it()` is only available in test mode.') + } } From 2937374bf897e431a9a89aa14c12d3ee0a940356 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 14:17:43 +0100 Subject: [PATCH 39/47] docs: add advanced docs API --- docs/.vitepress/config.ts | 16 +++++++ docs/advanced/api.md | 67 ++++++++++++++++++++++++++++ docs/advanced/runner.md | 94 +++++++++++++++++++++++++++++++++++++++ docs/guide/ui.md | 2 +- 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 docs/advanced/api.md create mode 100644 docs/advanced/runner.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 981a5366006b..287a605fd52b 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -85,6 +85,7 @@ export default withPwa(defineConfig({ { text: 'Guide', link: '/guide/' }, { text: 'API', link: '/api/' }, { text: 'Config', link: '/config/' }, + { text: 'Advanced', link: '/advanced/api' }, { text: `v${version}`, items: [ @@ -102,6 +103,21 @@ export default withPwa(defineConfig({ sidebar: { // TODO: bring sidebar of apis and config back + '/advanced': [ + { + text: 'Advanced', + items: [ + { + text: 'Vitest Node API', + link: '/advanced/api', + }, + { + text: 'Runner API', + link: '/advanced/runner', + }, + ], + }, + ], '/': [ { text: 'Guide', diff --git a/docs/advanced/api.md b/docs/advanced/api.md new file mode 100644 index 000000000000..ce36b7ae8fde --- /dev/null +++ b/docs/advanced/api.md @@ -0,0 +1,67 @@ +# Node API + +::: warning +Vitest exposes experimental private API. Breaking changes might not follow semver, please pin Vitest's version when using it. +::: + +## startVitest + +You can start running Vitest tests using its Node API: + +```js +import { startVitest } from 'vitest/node' + +const vitest = await startVitest('test', ['tests/run-only.test.ts']) + +await vitest?.close() +``` + +`startVitest` function returns `Vitest` instance if tests can be started. It returns `undefined`, if one of the following occurs: + +- Vitest didn't find "vite" package (usually installed with Vitest) +- If coverage is enabled and run mode is "test", but the coverage package is not installed (`@vitest/coverage-c8` or `@vitest/coverage-istanbul`) +- If the environment package is not installed (`jsdom`/`happy-dom`/`@edge-runtime/vm`) + +If `undefined` is returned or tests failed during the run, Vitest sets `process.exitCode` to `1`. + +If watch mode is not enabled, Vitest will call `close` method. + +If watch mode is enabled and the terminal supports TTY, Vitest will register console shortcuts. + +## createVitest + +You can create Vitest instance yourself using `createVitest` function. It returns the same `Vitest` instance as `startVitest`, but it doesn't start tests and doesn't validate installed packages. + +```js +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test', { + watch: false, +}) +``` + +## Vitest + +Vitest instance requires the current test mode. I can be either: + +- `test` when running runtime tests +- `benchmark` when running benchmarks +- `typecheck` when running type tests + +### mode + +#### test + +Test mode will only call functions inside `test` or `it`, and throws an error when `bench` is encountered. This mode uses `include` and `exclude` options in the config to find test files. + +#### benchmark + +Benchmark mode calls `bench` functions and throws an error, when it encounters `test` or `it`. This mode uses `benchmark.include` and `benchmark.exclude` options in the config to find benchmark files. + +#### typecheck + +Typecheck mode doesn't _run_ tests. It only analyses types and gives a summary. This mode uses `typecheck.include` and `typecheck.exclude` options in the config to find files to analyze. + +### start + +You can start running tests or benchmarks with `start` method. You can pass an array of strings to filter test files. diff --git a/docs/advanced/runner.md b/docs/advanced/runner.md new file mode 100644 index 000000000000..80fd7581a58b --- /dev/null +++ b/docs/advanced/runner.md @@ -0,0 +1,94 @@ +# Test Runner + +::: warning +This is advanced API. If you are just running tests, you probably don't need this. It is primarily used by library authors. +::: + +You can specify a path to your test runner with the `runner` option in your configuration file. This file should have a default export with a class implementing these methods: + +```ts +export interface VitestRunner { + /** + * First thing that's getting called before actually collecting and running tests. + */ + onBeforeCollect?(paths: string[]): unknown + /** + * Called after collecting tests and before "onBeforeRun". + */ + onCollected?(files: File[]): unknown + + /** + * Called before running a single test. Doesn't have "result" yet. + */ + onBeforeRunTest?(test: Test): unknown + /** + * Called before actually running the test function. Already has "result" with "state" and "startTime". + */ + onBeforeTryTest?(test: Test, retryCount: number): unknown + /** + * Called after result and state are set. + */ + onAfterRunTest?(test: Test): unknown + /** + * Called right after running the test function. Doesn't have new state yet. Will not be called, if the test function throws. + */ + onAfterTryTest?(test: Test, retryCount: number): unknown + + /** + * Called before running a single suite. Doesn't have "result" yet. + */ + onBeforeRunSuite?(suite: Suite): unknown + /** + * Called after running a single suite. Has state and result. + */ + onAfterRunSuite?(suite: Suite): unknown + + /** + * If defined, will be called instead of usual Vitest suite partition and handling. + * "before" and "after" hooks will not be ignored. + */ + runSuite?(suite: Suite): Promise + /** + * If defined, will be called instead of usual Vitest handling. Useful, if you have your custom test function. + * "before" and "after" hooks will not be ignored. + */ + runTest?(test: Test): Promise + + /** + * Called, when a task is updated. The same as "onTaskUpdate" in a reporter, but this is running in the same thread as tests. + */ + onTaskUpdate?(task: [string, TaskResult | undefined][]): Promise + + /** + * Called before running all tests in collected paths. + */ + onBeforeRun?(files: File[]): unknown + /** + * Called right after running all tests in collected paths. + */ + onAfterRun?(files: File[]): unknown + /** + * Called when new context for a test is defined. Useful, if you want to add custom properties to the context. + * If you only want to define custom context with a runner, consider using "beforeAll" in "setupFiles" instead. + */ + extendTestContext?(context: TestContext): TestContext + /** + * Called, when files are imported. Can be called in two situations: when collecting tests and when importing setup files. + */ + importFile(filepath: string, source: VitestRunnerImportSource): unknown + /** + * Publically available configuration. + */ + config: VitestRunnerConfig +} +``` + +When initiating this class, Vitest passes down Vitest config, - you should expose it as a `config` property. + +::: warning +`importFile` method in your custom runner must be inlined in `deps.inline` config option, if you call Node `import` inside. +::: + +::: tip +Snapshot support, C8 coverage, and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `VitestTestRunner` imported from `vitest/runners`. It also exposes `BenchmarkNodeRunner`, if you want to extend its functionality. +::: \ No newline at end of file diff --git a/docs/guide/ui.md b/docs/guide/ui.md index 7b2646868fff..18f3595c5f84 100644 --- a/docs/guide/ui.md +++ b/docs/guide/ui.md @@ -22,7 +22,7 @@ Then you can visit the Vitest UI at Vitest UI -Since Vitest 0.26.0, UI can also be used as a reporter. Use `'html'` reporter in your Vitest configuration to generate HTML output and preview results of your tests: +Since Vitest 0.26.0, UI can also be used as a reporter. Use `'html'` reporter in your Vitest configuration to generate HTML output and preview the results of your tests: ```ts // vitest.config.ts From 55c184db4f255ee7ec878721e63205bfed318351 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 15:38:23 +0100 Subject: [PATCH 40/47] chore: cleanup --- packages/vitest/src/node/reporters/base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index 5a5fd897655f..b78ae626825d 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -103,7 +103,7 @@ export abstract class BaseReporter implements Reporter { else this.ctx.logger.log(WAIT_FOR_CHANGE_PASS) - const hints = [] + const hints: string[] = [] // TODO typecheck doesn't support these for now if (this.mode !== 'typecheck') hints.push(HELP_HINT) From a833777f6c7754fdc8ca18aa86ab121155f6b440 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 15:58:54 +0100 Subject: [PATCH 41/47] build: fix unresolved build --- packages/vitest/src/node/reporters/junit.ts | 6 ++++-- packages/vitest/src/node/reporters/tap-flat.ts | 2 +- packages/vitest/src/node/reporters/tap.ts | 4 +++- packages/web-worker/rollup.config.js | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/vitest/src/node/reporters/junit.ts b/packages/vitest/src/node/reporters/junit.ts index 7fccb602281b..4a61d1adf731 100644 --- a/packages/vitest/src/node/reporters/junit.ts +++ b/packages/vitest/src/node/reporters/junit.ts @@ -2,8 +2,10 @@ import { existsSync, promises as fs } from 'node:fs' import { hostname } from 'node:os' import { dirname, relative, resolve } from 'pathe' +import type { Task } from '@vitest/runner' +import type { ErrorWithDiff } from '@vitest/runner/utils' import type { Vitest } from '../../node' -import type { ErrorWithDiff, Reporter, Task } from '../../types' +import type { Reporter } from '../../types/reporter' import { parseStacktrace } from '../../utils/source-map' import { F_POINTER } from '../../utils/figures' import { getOutputFile } from '../../utils/config-helpers' @@ -99,7 +101,7 @@ export class JUnitReporter implements Reporter { } async writeElement(name: string, attrs: Record, children: () => Promise) { - const pairs = [] + const pairs: string[] = [] for (const key in attrs) { const attr = attrs[key] if (attr === undefined) diff --git a/packages/vitest/src/node/reporters/tap-flat.ts b/packages/vitest/src/node/reporters/tap-flat.ts index 86ac59bbcaf3..125c41ebcff3 100644 --- a/packages/vitest/src/node/reporters/tap-flat.ts +++ b/packages/vitest/src/node/reporters/tap-flat.ts @@ -1,5 +1,5 @@ +import type { Task } from '@vitest/runner' import type { Vitest } from '../../node' -import type { Task } from '../../types' import { TapReporter } from './tap' function flattenTasks(task: Task, baseName = ''): Task[] { diff --git a/packages/vitest/src/node/reporters/tap.ts b/packages/vitest/src/node/reporters/tap.ts index 8bf12cdd0ac9..d3e5540a167c 100644 --- a/packages/vitest/src/node/reporters/tap.ts +++ b/packages/vitest/src/node/reporters/tap.ts @@ -1,5 +1,7 @@ +import type { Task } from '@vitest/runner' +import type { ParsedStack } from '@vitest/runner/utils' import type { Vitest } from '../../node' -import type { ParsedStack, Reporter, Task } from '../../types' +import type { Reporter } from '../../types/reporter' import { parseStacktrace } from '../../utils/source-map' import { IndentedLogger } from './renderers/indented-logger' diff --git a/packages/web-worker/rollup.config.js b/packages/web-worker/rollup.config.js index 92aee2ef6e24..8bd2ad296f88 100644 --- a/packages/web-worker/rollup.config.js +++ b/packages/web-worker/rollup.config.js @@ -44,7 +44,7 @@ export default () => [ plugins, }, { - input: entries, + input: 'src/pure.ts', output: { dir: process.cwd(), entryFileNames: '[name].d.ts', From 6a6f051956aa65f15e4bba973c7d4f2b3cacab65 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 16:02:36 +0100 Subject: [PATCH 42/47] chore: fix bench lockfile --- bench/pnpm-lock.yaml | 161 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 135 insertions(+), 26 deletions(-) diff --git a/bench/pnpm-lock.yaml b/bench/pnpm-lock.yaml index c414ca2efa3c..3ca596138bc0 100644 --- a/bench/pnpm-lock.yaml +++ b/bench/pnpm-lock.yaml @@ -51,6 +51,7 @@ importers: ../packages/browser: specifiers: '@types/ws': ^8.5.4 + '@vitest/runner': workspace:* '@vitest/ws-client': workspace:* local-pkg: ^0.4.2 mlly: ^1.1.0 @@ -60,6 +61,7 @@ importers: sirv: ^2.0.2 vitest: workspace:* dependencies: + '@vitest/runner': link:../runner local-pkg: 0.4.2 mlly: 1.1.0 modern-node-polyfills: 0.0.9 @@ -74,14 +76,14 @@ importers: ../packages/coverage-c8: specifiers: c8: ^7.12.0 - pathe: ^0.2.0 + pathe: ^1.1.0 vite-node: workspace:* vitest: workspace:* dependencies: c8: 7.12.0 vitest: link:../vitest devDependencies: - pathe: 0.2.0 + pathe: 1.1.0 vite-node: link:../vite-node ../packages/coverage-istanbul: @@ -96,7 +98,7 @@ importers: istanbul-lib-report: ^3.0.0 istanbul-lib-source-maps: ^4.0.1 istanbul-reports: ^3.1.5 - pathe: ^0.2.0 + pathe: ^1.1.0 test-exclude: ^6.0.0 vitest: workspace:* dependencies: @@ -113,7 +115,7 @@ importers: '@types/istanbul-lib-report': 3.0.0 '@types/istanbul-lib-source-maps': 4.0.1 '@types/istanbul-reports': 3.0.1 - pathe: 0.2.0 + pathe: 1.1.0 ../packages/expect: specifiers: @@ -125,8 +127,19 @@ importers: '@vitest/spy': link:../spy '@vitest/utils': link:../utils chai: 4.3.7 + devDependencies: picocolors: 1.0.0 + ../packages/runner: + specifiers: + '@vitest/utils': workspace:* + p-limit: ^4.0.0 + pathe: ^1.1.0 + dependencies: + '@vitest/utils': link:../utils + p-limit: 4.0.0 + pathe: 1.1.0 + ../packages/spy: specifiers: tinyspy: ^1.0.2 @@ -144,6 +157,7 @@ importers: '@unocss/reset': ^0.48.3 '@vitejs/plugin-vue': ^4.0.0 '@vitejs/plugin-vue-jsx': ^3.0.0 + '@vitest/runner': workspace:* '@vitest/ws-client': workspace:* '@vueuse/core': ^9.10.0 ansi-to-html: ^0.7.2 @@ -156,6 +170,7 @@ importers: fast-glob: ^3.2.12 flatted: ^3.2.7 floating-vue: ^2.0.0-y.0 + pathe: ^1.1.0 picocolors: ^1.0.0 rollup: ^2.79.1 sirv: ^2.0.2 @@ -170,6 +185,8 @@ importers: dependencies: fast-glob: 3.2.12 flatted: 3.2.7 + pathe: 1.1.0 + picocolors: 1.0.0 sirv: 2.0.2 devDependencies: '@faker-js/faker': 7.6.0 @@ -181,6 +198,7 @@ importers: '@unocss/reset': 0.48.3 '@vitejs/plugin-vue': 4.0.0_vite@4.0.0+vue@3.2.45 '@vitejs/plugin-vue-jsx': 3.0.0_vite@4.0.0+vue@3.2.45 + '@vitest/runner': link:../runner '@vitest/ws-client': link:../ws-client '@vueuse/core': 9.10.0_vue@3.2.45 ansi-to-html: 0.7.2 @@ -191,7 +209,6 @@ importers: d3-graph-controller: 2.5.1 diff: 5.1.0 floating-vue: 2.0.0-y.0_vue@3.2.45 - picocolors: 1.0.0 rollup: 2.79.1 splitpanes: 3.1.5 unocss: 0.48.3_rollup@2.79.1+vite@4.0.0 @@ -207,11 +224,13 @@ importers: '@types/diff': ^5.0.2 cli-truncate: ^3.1.0 diff: ^5.1.0 + loupe: ^2.3.6 picocolors: ^1.0.0 pretty-format: ^27.5.1 dependencies: cli-truncate: 3.1.0 diff: 5.1.0 + loupe: 2.3.6 picocolors: 1.0.0 pretty-format: 27.5.1 devDependencies: @@ -225,7 +244,7 @@ importers: cac: ^6.7.14 debug: ^4.3.4 mlly: ^1.1.0 - pathe: ^0.2.0 + pathe: ^1.1.0 picocolors: ^1.0.0 rollup: ^2.79.1 source-map: ^0.6.1 @@ -235,7 +254,7 @@ importers: cac: 6.7.14 debug: 4.3.4 mlly: 1.1.0 - pathe: 0.2.0 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 source-map-support: 0.5.21 @@ -262,6 +281,7 @@ importers: '@types/sinonjs__fake-timers': ^8.1.2 '@vitest/browser': '*' '@vitest/expect': workspace:* + '@vitest/runner': workspace:* '@vitest/spy': workspace:* '@vitest/ui': workspace:* '@vitest/utils': workspace:* @@ -290,13 +310,14 @@ importers: mlly: ^1.1.0 natural-compare: ^1.4.0 p-limit: ^4.0.0 - pathe: ^0.2.0 + pathe: ^1.1.0 picocolors: ^1.0.0 pkg-types: ^1.0.1 pretty-format: ^27.5.1 prompts: ^2.4.2 rollup: ^2.79.1 source-map: ^0.6.1 + std-env: ^3.3.1 strip-ansi: ^7.0.1 strip-literal: ^1.0.0 tinybench: ^2.3.1 @@ -305,26 +326,35 @@ importers: typescript: ^4.9.4 vite: ^3.0.0 || ^4.0.0 vite-node: workspace:* + why-is-node-running: ^2.2.2 ws: ^8.12.0 dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 17.0.35 '@vitest/browser': link:../browser + '@vitest/expect': link:../expect + '@vitest/runner': link:../runner + '@vitest/spy': link:../spy + '@vitest/ui': link:../ui + '@vitest/utils': link:../utils acorn: 8.8.1 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 debug: 4.3.4 local-pkg: 0.4.2 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 + std-env: 3.3.1 strip-literal: 1.0.0 tinybench: 2.3.1 tinypool: 0.3.0 tinyspy: 1.0.2 vite: 3.2.5_@types+node@17.0.35 vite-node: link:../vite-node + why-is-node-running: 2.2.2 devDependencies: '@antfu/install-pkg': 0.1.1 '@edge-runtime/vm': 2.0.2 @@ -335,10 +365,6 @@ importers: '@types/natural-compare': 1.4.1 '@types/prompts': 2.4.2 '@types/sinonjs__fake-timers': 8.1.2 - '@vitest/expect': link:../expect - '@vitest/spy': link:../spy - '@vitest/ui': link:../ui - '@vitest/utils': link:../utils birpc: 0.2.3 chai-subset: 1.6.0 cli-truncate: 3.1.0 @@ -358,7 +384,6 @@ importers: mlly: 1.1.0 natural-compare: 1.4.0 p-limit: 4.0.0 - pathe: 0.2.0 pkg-types: 1.0.1 pretty-format: 27.5.1 prompts: 2.4.2 @@ -388,6 +413,7 @@ importers: ../packages/ws-client: specifiers: + '@vitest/runner': workspace:* birpc: ^0.2.3 flatted: ^3.2.7 rollup: ^2.79.1 @@ -397,6 +423,7 @@ importers: flatted: 3.2.7 ws: 8.12.0 devDependencies: + '@vitest/runner': link:../runner rollup: 2.79.1 test/vue: @@ -652,6 +679,24 @@ packages: - supports-color dev: true + /@babel/helper-create-class-features-plugin/7.18.0_@babel+core@7.20.5: + resolution: {integrity: sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-member-expression-to-functions': 7.17.7 + '@babel/helper-optimise-call-expression': 7.16.7 + '@babel/helper-replace-supers': 7.16.7 + '@babel/helper-split-export-declaration': 7.18.6 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-create-class-features-plugin/7.20.5_@babel+core@7.18.2: resolution: {integrity: sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==} engines: {node: '>=6.9.0'} @@ -1748,6 +1793,16 @@ packages: '@babel/helper-plugin-utils': 7.17.12 dev: true + /@babel/plugin-syntax-typescript/7.17.12_@babel+core@7.20.5: + resolution: {integrity: sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.17.12 + dev: true + /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.20.5: resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} engines: {node: '>=6.9.0'} @@ -2115,6 +2170,21 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-commonjs/7.17.7_@babel+core@7.20.5: + resolution: {integrity: sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-simple-access': 7.18.2 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs/7.18.2_@babel+core@7.18.2: resolution: {integrity: sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==} engines: {node: '>=6.9.0'} @@ -2469,6 +2539,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-typescript/7.18.4_@babel+core@7.20.5: + resolution: {integrity: sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.18.0_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.17.12 + '@babel/plugin-syntax-typescript': 7.17.12_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-typescript/7.20.2_@babel+core@7.20.5: resolution: {integrity: sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag==} engines: {node: '>=6.9.0'} @@ -2744,9 +2828,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.5 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-validator-option': 7.18.6 - '@babel/plugin-transform-typescript': 7.20.2_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-validator-option': 7.16.7 + '@babel/plugin-transform-typescript': 7.18.4_@babel+core@7.20.5 transitivePeerDependencies: - supports-color dev: true @@ -3901,7 +3985,7 @@ packages: consola: 2.15.3 fast-glob: 3.2.12 magic-string: 0.27.0 - pathe: 1.0.0 + pathe: 1.1.0 perfect-debounce: 0.1.3 transitivePeerDependencies: - rollup @@ -4225,11 +4309,11 @@ packages: optional: true dependencies: '@babel/core': 7.20.5 - '@babel/plugin-transform-modules-commonjs': 7.18.2_@babel+core@7.20.5 + '@babel/plugin-transform-modules-commonjs': 7.17.7_@babel+core@7.20.5 babel-jest: 27.5.1_@babel+core@7.20.5 chalk: 2.4.2 convert-source-map: 1.8.0 - css-tree: 2.3.1 + css-tree: 2.1.0 jest: 27.5.1 source-map: 0.5.6 ts-jest: 27.1.5_lymxezh3rizc3sf4nv2timwyqu @@ -7547,6 +7631,12 @@ packages: get-func-name: 2.0.0 dev: false + /loupe/2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + dependencies: + get-func-name: 2.0.0 + dev: false + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -7840,7 +7930,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.0.0 - dev: true /p-locate/4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} @@ -7924,12 +8013,12 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - /pathe/0.2.0: - resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} - /pathe/1.0.0: resolution: {integrity: sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==} + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + /pathval/1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: false @@ -7975,7 +8064,7 @@ packages: dependencies: jsonc-parser: 3.2.0 mlly: 1.1.0 - pathe: 1.0.0 + pathe: 1.1.0 /platform/1.3.6: resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} @@ -8286,6 +8375,10 @@ packages: object-inspect: 1.12.0 dev: true + /siginfo/2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: false + /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -8390,6 +8483,14 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stackback/0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: false + + /std-env/3.3.1: + resolution: {integrity: sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==} + dev: false + /streamsearch/1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -8804,7 +8905,7 @@ packages: local-pkg: 0.4.2 magic-string: 0.27.0 mlly: 1.1.0 - pathe: 1.0.0 + pathe: 1.1.0 pkg-types: 1.0.1 scule: 1.0.0 strip-literal: 1.0.0 @@ -9276,6 +9377,15 @@ packages: dependencies: isexe: 2.0.0 + /why-is-node-running/2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: false + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} @@ -9400,4 +9510,3 @@ packages: /yocto-queue/1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - dev: true From 99579fb3e6a0273107b2243657e98caeb12f6c09 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 16:43:41 +0100 Subject: [PATCH 43/47] refactor: remove comments, cleanup --- .../vitest/src/integrations/snapshot/port/state.ts | 14 ++++++++------ packages/vitest/src/runtime/setup.common.ts | 2 +- packages/vitest/src/runtime/setup.node.ts | 2 +- packages/vitest/src/utils/index.ts | 1 - test/core/vitest.config.ts | 1 - 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/vitest/src/integrations/snapshot/port/state.ts b/packages/vitest/src/integrations/snapshot/port/state.ts index 2d3596773247..de7ad8c84143 100644 --- a/packages/vitest/src/integrations/snapshot/port/state.ts +++ b/packages/vitest/src/integrations/snapshot/port/state.ts @@ -48,7 +48,7 @@ export default class SnapshotState { private _uncheckedKeys: Set private _snapshotFormat: PrettyFormatOptions private _environment: SnapshotEnvironment - private _exists: boolean + private _fileExists: boolean added: number expand: boolean @@ -66,7 +66,7 @@ export default class SnapshotState { snapshotContent, options, ) - this._exists = snapshotContent != null // TODO: update on watch? + this._fileExists = snapshotContent != null // TODO: update on watch? this._initialData = data this._snapshotData = data this._dirty = dirty @@ -167,17 +167,19 @@ export default class SnapshotState { } if ((this._dirty || this._uncheckedKeys.size) && !isEmpty) { - if (hasExternalSnapshots) + if (hasExternalSnapshots) { await saveSnapshotFile(this._snapshotData, this.snapshotPath) + this._fileExists = true + } if (hasInlineSnapshots) await saveInlineSnapshots(this._inlineSnapshots) status.saved = true } - else if (!hasExternalSnapshots && this._exists) { + else if (!hasExternalSnapshots && this._fileExists) { if (this._updateSnapshot === 'all') { await this._environment.removeSnapshotFile(this.snapshotPath) - this._exists = false + this._fileExists = false } status.deleted = true @@ -227,7 +229,7 @@ export default class SnapshotState { const expectedTrimmed = prepareExpected(expected) const pass = expectedTrimmed === prepareExpected(receivedSerialized) const hasSnapshot = expected !== undefined - const snapshotIsPersisted = isInline || this._exists + const snapshotIsPersisted = isInline || this._fileExists if (pass && !isInline) { // Executing a snapshot file as JavaScript and writing the strings back diff --git a/packages/vitest/src/runtime/setup.common.ts b/packages/vitest/src/runtime/setup.common.ts index bc1fcb4d127c..32d8f5fc2096 100644 --- a/packages/vitest/src/runtime/setup.common.ts +++ b/packages/vitest/src/runtime/setup.common.ts @@ -10,8 +10,8 @@ export async function setupCommonEnv(config: ResolvedConfig) { if (globalSetup) return - setSafeTimers() globalSetup = true + setSafeTimers() if (config.globals) (await import('../integrations/globals')).registerApiGlobally() diff --git a/packages/vitest/src/runtime/setup.node.ts b/packages/vitest/src/runtime/setup.node.ts index a76c8c49547a..b7494cad5a6d 100644 --- a/packages/vitest/src/runtime/setup.node.ts +++ b/packages/vitest/src/runtime/setup.node.ts @@ -26,9 +26,9 @@ export async function setupGlobalEnv(config: ResolvedConfig) { if (globalSetup) return + globalSetup = true setupSnapshotEnvironment(new NodeSnapshotEnvironment()) setColors(p) - globalSetup = true const require = createRequire(import.meta.url) // always mock "required" `css` files, because we cannot process them diff --git a/packages/vitest/src/utils/index.ts b/packages/vitest/src/utils/index.ts index cfea2032a5a7..728d6fee66ea 100644 --- a/packages/vitest/src/utils/index.ts +++ b/packages/vitest/src/utils/index.ts @@ -1,4 +1,3 @@ -// import { relative as relativeBrowser } from 'node:path' import { relative } from 'pathe' import type { ModuleCacheMap } from 'vite-node' import { getWorkerState } from '../utils' diff --git a/test/core/vitest.config.ts b/test/core/vitest.config.ts index c1f21c8e9014..4a675e9e7b84 100644 --- a/test/core/vitest.config.ts +++ b/test/core/vitest.config.ts @@ -35,7 +35,6 @@ export default defineConfig({ alias: [ { find: '#', replacement: resolve(__dirname, 'src') }, { find: '$', replacement: 'src' }, - // { find: '@vitest', replacement: resolve(__dirname, '..', '..', 'packages') }, ], }, test: { From b7ddd366a486c1de226853ae3dec8ff8bea9cfbb Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 16:43:52 +0100 Subject: [PATCH 44/47] fix: collect custom tests --- packages/runner/src/collect.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/runner/src/collect.ts b/packages/runner/src/collect.ts index ea2ea2633d2c..e363fe319fe0 100644 --- a/packages/runner/src/collect.ts +++ b/packages/runner/src/collect.ts @@ -46,6 +46,9 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi if (c.type === 'test') { file.tasks.push(c) } + else if (c.type === 'custom') { + file.tasks.push(c) + } else if (c.type === 'suite') { file.tasks.push(c) } From 9bb8bf7b95a5c573e41b83d5f84d28140dcf62c9 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 16:45:15 +0100 Subject: [PATCH 45/47] refactor: validate custom test runner --- packages/vitest/src/runtime/entry.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index 6a2f3bd9aa3c..e2c8591b9950 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -22,16 +22,24 @@ function groupBy(collection: T[], iterate } async function getTestRunnerConstructor(config: ResolvedConfig): Promise { - if (config.runner) - // TODO: validation - return (await import(config.runner)).default - return (config.mode === 'test' ? VitestTestRunner : NodeBenchmarkRunner) as any as VitestRunnerConstructor + if (!config.runner) + return (config.mode === 'test' ? VitestTestRunner : NodeBenchmarkRunner) as any as VitestRunnerConstructor + const mod = await import(config.runner) + if (!mod.default && typeof mod.default !== 'function') + throw new Error(`Runner must export a default function, but got ${typeof mod.default} imported from ${config.runner}`) + return mod.default as VitestRunnerConstructor } async function getTestRunner(config: ResolvedConfig): Promise { const TestRunner = await getTestRunnerConstructor(config) const testRunner = new TestRunner(config) + if (!testRunner.config) + testRunner.config = config + + if (!testRunner.importFile) + throw new Error('Runner must implement "importFile" method.') + // patch some methods, so custom runners don't need to call RPC const originalOnTaskUpdate = testRunner.onTaskUpdate testRunner.onTaskUpdate = async (task) => { @@ -62,7 +70,7 @@ export async function run(files: string[], config: ResolvedConfig): Promise Date: Sat, 21 Jan 2023 17:13:27 +0100 Subject: [PATCH 46/47] chore: more validation and documentation --- docs/advanced/runner.md | 58 ++++++++++++++++++++++++++++++-- packages/runner/README.md | 4 +++ packages/runner/src/index.ts | 1 + packages/runner/src/run.ts | 11 ++++-- packages/runner/src/suite.ts | 6 ++-- packages/vitest/LICENSE.md | 38 --------------------- packages/vitest/package.json | 4 +++ packages/vitest/rollup.config.js | 4 +++ packages/vitest/src/suite.ts | 2 ++ packages/vitest/suite.d.ts | 1 + 10 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 packages/vitest/src/suite.ts create mode 100644 packages/vitest/suite.d.ts diff --git a/docs/advanced/runner.md b/docs/advanced/runner.md index 80fd7581a58b..5d45c023e21d 100644 --- a/docs/advanced/runner.md +++ b/docs/advanced/runner.md @@ -90,5 +90,59 @@ When initiating this class, Vitest passes down Vitest config, - you should expos ::: ::: tip -Snapshot support, C8 coverage, and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `VitestTestRunner` imported from `vitest/runners`. It also exposes `BenchmarkNodeRunner`, if you want to extend its functionality. -::: \ No newline at end of file +Snapshot support and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `VitestTestRunner` imported from `vitest/runners`. It also exposes `BenchmarkNodeRunner`, if you want to extend benchmark functionality. +::: + +## Your task function + +You can extend Vitest task system with your tasks. A task is an object that is part of a suite. It is automatically added to the current suite with a `suite.custom` method: + +```js +// ./utils/custom.js +import { getCurrentSuite, setFn } from 'vitest/suite' +export { describe, beforeAll, afterAll } from 'vitest' + +// this function will be called, when Vitest collects tasks +export const myCustomTask = function (name, fn) { + const task = getCurrentSuite().custom(name) + task.meta = { + customPropertyToDifferentiateTask: true + } + setFn(task, fn || (() => {})) +} +``` + +```js +// ./garden/tasks.test.js +import { afterAll, beforeAll, describe, myCustomTask } from '../utils/custom.js' +import { gardener } from './gardener.js' + +deccribe('take care of the garden', () => { + beforeAll(() => { + gardener.putWorkingClothes() + }) + + myCustomTask('weed the grass', () => { + gardener.weedTheGrass() + }) + myCustomTask('water flowers', () => { + gardener.waterFlowers() + }) + + afterAll(() => { + gardener.goHome() + }) +}) +``` + +```bash +vitest ./garder/tasks.test.js +``` + +::: warning +If you don't have a custom runner or didn't define `runTest` method, Vitest will try to retrieve a task automatically. If you didn't add a function with `setFn`, it will fail. +::: + +::: tip +Custom task system supports hooks and contexts. If you want to support property chaining (like, `only`, `skip`, and your custom ones), you can import `createChainable` from `vitest/suite` and wrap your function with it. You will need to call `custom` as `custom.call(this)`, if you decide to do this. +::: diff --git a/packages/runner/README.md b/packages/runner/README.md index 9b17bf5a4d1d..2796b6aacd03 100644 --- a/packages/runner/README.md +++ b/packages/runner/README.md @@ -1 +1,5 @@ # @vitest/runner + +Vitest mechanism to collect and run tasks. + +[GitHub](https://github.com/vitest-dev/vitest) | [Documentation](https://vitest.dev/advanced/runner) diff --git a/packages/runner/src/index.ts b/packages/runner/src/index.ts index fa84ec6e0a47..8860e67d0e9c 100644 --- a/packages/runner/src/index.ts +++ b/packages/runner/src/index.ts @@ -1,4 +1,5 @@ export { startTests, updateTask } from './run' export { test, it, describe, suite, getCurrentSuite } from './suite' export { beforeAll, beforeEach, afterAll, afterEach, onTestFailed } from './hooks' +export { setFn, getFn } from './map' export * from './types' diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index 6e5c814c6592..acca6bd8f6e4 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -135,10 +135,15 @@ export async function runTest(test: Test, runner: VitestRunner) { test.result.retryCount = retryCount - if (runner.runTest) + if (runner.runTest) { await runner.runTest(test) - else - await getFn(test)() + } + else { + const fn = getFn(test) + if (!fn) + throw new Error('Test function is not found. Did you add it using `setFn`?') + await fn() + } await runner.onAfterTryTest?.(test, retryCount) diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index c2c0c544dcfe..11a25d13c7f9 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -95,13 +95,13 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m tasks.push(test) }) - // TODO: document how it can be used to extend native runner - const custom = function (this: Record, name: string) { + const custom = function (this: Record, name = '') { + const self = this || {} const task: TaskCustom = { id: '', name, type: 'custom', - mode: this.only ? 'only' : this.skip ? 'skip' : this.todo ? 'todo' : 'run', + mode: self.only ? 'only' : self.skip ? 'skip' : self.todo ? 'todo' : 'run', } tasks.push(task) return task diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index 2c5e64c44482..5d7e1b156bf8 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -427,44 +427,6 @@ Repository: git@github.com:moxystudio/node-cross-spawn.git --------------------------------------- -## diff -License: BSD-3-Clause -Repository: git://github.com/kpdecker/jsdiff.git - -> Software License Agreement (BSD License) -> -> Copyright (c) 2009-2015, Kevin Decker -> -> All rights reserved. -> -> Redistribution and use of this software in source and binary forms, with or without modification, -> are permitted provided that the following conditions are met: -> -> * Redistributions of source code must retain the above -> copyright notice, this list of conditions and the -> following disclaimer. -> -> * Redistributions in binary form must reproduce the above -> copyright notice, this list of conditions and the -> following disclaimer in the documentation and/or other -> materials provided with the distribution. -> -> * Neither the name of Kevin Decker nor the names of its -> contributors may be used to endorse or promote products -> derived from this software without specific prior -> written permission. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -> IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -> FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -> CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -> DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -> IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -> OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------- - ## eastasianwidth License: MIT By: Masaki Komagata diff --git a/packages/vitest/package.json b/packages/vitest/package.json index dbb0aaa663d7..b8fd9f03f23b 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -53,6 +53,10 @@ "types": "./dist/runners.d.ts", "import": "./dist/runners.js" }, + "./suite": { + "types": "./dist/suite.d.ts", + "import": "./dist/suite.js" + }, "./environments": { "types": "./dist/environments.d.ts", "import": "./dist/environments.js" diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index ca6bcf66a04e..d29c799a669b 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -18,6 +18,7 @@ const entries = [ 'src/node/cli.ts', 'src/node/cli-wrapper.ts', 'src/node.ts', + 'src/suite.ts', 'src/browser.ts', 'src/runners.ts', 'src/environments.ts', @@ -33,6 +34,7 @@ const dtsEntries = [ 'src/environments.ts', 'src/browser.ts', 'src/runners.ts', + 'src/suite.ts', 'src/config.ts', ] @@ -48,6 +50,8 @@ const external = [ 'vite-node/client', 'vite-node/server', 'vite-node/utils', + '@vitest/utils/diff', + '@vitest/runner/utils', '@vitest/runner/types', ] diff --git a/packages/vitest/src/suite.ts b/packages/vitest/src/suite.ts new file mode 100644 index 000000000000..c2f67b8dfe7f --- /dev/null +++ b/packages/vitest/src/suite.ts @@ -0,0 +1,2 @@ +export { getCurrentSuite, getFn, setFn } from '@vitest/runner' +export { createChainable } from '@vitest/runner/utils' diff --git a/packages/vitest/suite.d.ts b/packages/vitest/suite.d.ts new file mode 100644 index 000000000000..9465dc5c11d2 --- /dev/null +++ b/packages/vitest/suite.d.ts @@ -0,0 +1 @@ +export * from './dist/suite.js' From fe178528e4b6e9986e9d55ee5ade094ed164cffb Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 21 Jan 2023 17:31:01 +0100 Subject: [PATCH 47/47] docs: add link to advanced guide in the runner section --- docs/config/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 4a6c2a58ae0f..d3db7fefaf47 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -148,6 +148,13 @@ TypeError: default is not a function If you are using bundlers or transpilers that bypass this Node.js limitation, you can enable this option manually. By default, Vitest assumes you are using Node ESM syntax, when `environment` is `node`, and doesn't interpret named exports. +### runner + +- **Type**: `VitestRunnerConstructor` +- **Default**: `node`, when running tests, or `benchmark`, when running benchmarks + +Path to a custom test runner. This is an advanced feature and should be used with custom library runners. You can read more about it in [the documentation](/advanced/runner). + ### benchmark - **Type:** `{ include?, exclude?, ... }` @@ -1053,13 +1060,6 @@ Options to configure Vitest cache policy. At the moment Vitest stores cache for Path to cache directory. -### runner - -- **Type**: `VitestRunnerConstructor` -- **Default**: `node`, when running tests, or `benchmark`, when running benchmarks - -Path to custom test runner. - ### sequence - **Type**: `{ sequencer?, shuffle?, seed?, hooks? }`