diff --git a/packages/core/package.json b/packages/core/package.json index 64a9801c..e8532ad4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@rstest/core", - "version": "0.0.4", + "version": "0.0.6", "description": "The Rsbuild-based test tool.", "bugs": { "url": "https://github.com/web-infra-dev/rstest/issues" @@ -65,19 +65,20 @@ "tinypool": "^1.1.1" }, "devDependencies": { - "@sinonjs/fake-timers": "^14.0.0", "@babel/code-frame": "^7.27.1", "@jridgewell/trace-mapping": "0.3.29", "@microsoft/api-extractor": "^7.52.8", "@rslib/core": "0.10.4", "@rstest/tsconfig": "workspace:*", + "@sinonjs/fake-timers": "^14.0.0", "@types/babel__code-frame": "^7.0.6", - "@types/sinonjs__fake-timers": "^8.1.5", "@types/jsdom": "^21.1.7", - "jsdom": "^26.1.0", + "@types/sinonjs__fake-timers": "^8.1.5", "@types/source-map-support": "^0.5.10", "cac": "^6.7.14", + "happy-dom": "^18.0.1", "jest-diff": "^30.0.3", + "jsdom": "^26.1.0", "license-webpack-plugin": "^4.0.2", "picocolors": "^1.1.1", "rslog": "^1.2.9", @@ -87,9 +88,13 @@ "tinyspy": "^4.0.3" }, "peerDependencies": { + "happy-dom": "*", "jsdom": "*" }, "peerDependenciesMeta": { + "happy-dom": { + "optional": true + }, "jsdom": { "optional": true } diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 740dccae..b844c73f 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -4,7 +4,7 @@ import { normalize } from 'pathe'; import { isCI } from 'std-env'; import { loadConfig } from '../config'; import type { ListCommandOptions, RstestConfig } from '../types'; -import { formatError, getAbsolutePath } from '../utils/helper'; +import { castArray, formatError, getAbsolutePath } from '../utils/helper'; import { logger } from '../utils/logger'; import { showRstest } from './prepare'; @@ -13,12 +13,15 @@ type CommonOptions = { config?: string; configLoader?: LoadConfigOptions['loader']; globals?: boolean; + isolate?: boolean; + exclude?: string[]; passWithNoTests?: boolean; printConsoleTrace?: boolean; disableConsoleIntercept?: boolean; update?: boolean; testNamePattern?: RegExp | string; testTimeout?: number; + hookTimeout?: number; testEnvironment?: string; clearMocks?: boolean; resetMocks?: boolean; @@ -48,6 +51,8 @@ const applyCommonOptions = (cli: CAC) => { 'Specify the project root directory, can be an absolute path or a path relative to cwd', ) .option('--globals', 'Provide global APIs') + .option('--isolate', 'Run tests in an isolated environment') + .option('--exclude ', 'Exclude files from test') .option('-u, --update', 'Update snapshot files') .option( '--passWithNoTests', @@ -71,6 +76,7 @@ const applyCommonOptions = (cli: CAC) => { 'The environment that will be used for testing', ) .option('--testTimeout ', 'Timeout of a test in milliseconds') + .option('--hookTimeout ', 'Timeout of hook in milliseconds') .option('--retry ', 'Number of times to retry a test if it fails') .option('--maxConcurrency ', 'Maximum number of concurrent tests') .option( @@ -108,10 +114,12 @@ export async function initCli(options: CommonOptions): Promise<{ const keys: (keyof CommonOptions & keyof RstestConfig)[] = [ 'root', 'globals', + 'isolate', 'passWithNoTests', 'update', 'testNamePattern', 'testTimeout', + 'hookTimeout', 'clearMocks', 'resetMocks', 'restoreMocks', @@ -130,6 +138,10 @@ export async function initCli(options: CommonOptions): Promise<{ } } + if (options.exclude) { + config.exclude = castArray(options.exclude); + } + return { config, configFilePath, diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 9968f4aa..fbd7cc01 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -96,6 +96,7 @@ const createDefaultConfig = (): NormalizedConfig => ({ passWithNoTests: false, update: false, testTimeout: 5_000, + hookTimeout: 10_000, testEnvironment: 'node', retry: 0, reporters: ['default'], diff --git a/packages/core/src/core/rsbuild.ts b/packages/core/src/core/rsbuild.ts index 41d5b24f..11d786e6 100644 --- a/packages/core/src/core/rsbuild.ts +++ b/packages/core/src/core/rsbuild.ts @@ -143,6 +143,12 @@ export const prepareRsbuild = async ( printUrls: false, strictPort: false, middlewareMode: true, + compress: false, + cors: false, + publicDir: false, + }, + dev: { + hmr: false, }, performance, environments: { diff --git a/packages/core/src/pool/index.ts b/packages/core/src/pool/index.ts index 4c4a8cdb..284a3107 100644 --- a/packages/core/src/pool/index.ts +++ b/packages/core/src/pool/index.ts @@ -134,11 +134,13 @@ export const createPool = async ({ printConsoleTrace, disableConsoleIntercept, testEnvironment, + hookTimeout, } = context.normalizedConfig; const runtimeConfig = { testNamePattern, testTimeout, + hookTimeout, passWithNoTests, retry, globals, diff --git a/packages/core/src/reporter/index.ts b/packages/core/src/reporter/index.ts index f62772b3..a5ac44bb 100644 --- a/packages/core/src/reporter/index.ts +++ b/packages/core/src/reporter/index.ts @@ -74,7 +74,7 @@ export class DefaultReporter implements Reporter { const formatDuration = (duration: number) => { return color[duration > slowTestThreshold ? 'yellow' : 'green']( - `${prettyTime(duration, false)}`, + `${prettyTime(duration)}`, ); }; @@ -86,9 +86,13 @@ export class DefaultReporter implements Reporter { title += ` ${formatDuration(test.duration!)}`; } + const hasRetryCase = test.results.some( + (result) => (result.retryCount || 0) > 0, + ); + logger.log(title); - if (test.status !== 'fail' && !isTooSlow) { + if (test.status !== 'fail' && !isTooSlow && !hasRetryCase) { return; } @@ -100,7 +104,13 @@ export class DefaultReporter implements Reporter { for (const result of test.results) { const isSlowCase = (result.duration || 0) > slowTestThreshold; - if (!showAllCases && result.status !== 'fail' && !isSlowCase) { + const retried = (result.retryCount || 0) > 0; + if ( + !showAllCases && + result.status !== 'fail' && + !isSlowCase && + !retried + ) { continue; } const icon = @@ -110,7 +120,7 @@ export class DefaultReporter implements Reporter { const nameStr = getTaskNameWithPrefix(result); const duration = typeof result.duration !== 'undefined' - ? ` (${prettyTime(result.duration, false)})` + ? ` (${prettyTime(result.duration)})` : ''; const retry = result.retryCount ? color.yellow(` (retry x${result.retryCount})`) diff --git a/packages/core/src/runtime/runner/index.ts b/packages/core/src/runtime/runner/index.ts index 29f7c31c..43ee7239 100644 --- a/packages/core/src/runtime/runner/index.ts +++ b/packages/core/src/runtime/runner/index.ts @@ -25,11 +25,12 @@ export function createRunner({ workerState }: { workerState: WorkerState }): { } { const { testPath, - runtimeConfig: { testTimeout, testNamePattern }, + runtimeConfig: { testTimeout, testNamePattern, hookTimeout }, } = workerState; const runtime = createRuntimeAPI({ testPath, testTimeout, + hookTimeout, }); const testRunner: TestRunner = new TestRunner(); diff --git a/packages/core/src/runtime/runner/runtime.ts b/packages/core/src/runtime/runner/runtime.ts index 1b1072c8..0c6c5615 100644 --- a/packages/core/src/runtime/runner/runtime.ts +++ b/packages/core/src/runtime/runner/runtime.ts @@ -41,18 +41,21 @@ export class RunnerRuntime { */ private collectStatus: CollectStatus = 'lazy'; private currentCollectList: Array<() => MaybePromise> = []; - private defaultHookTimeout = 5_000; + private defaultHookTimeout; private defaultTestTimeout; constructor({ testPath, testTimeout, + hookTimeout, }: { testTimeout: number; + hookTimeout: number; testPath: string; }) { this.testPath = testPath; this.defaultTestTimeout = testTimeout; + this.defaultHookTimeout = hookTimeout; } afterAll( @@ -427,9 +430,11 @@ export class RunnerRuntime { export const createRuntimeAPI = ({ testPath, testTimeout, + hookTimeout, }: { testPath: string; testTimeout: number; + hookTimeout: number; }): { api: RunnerAPI; instance: RunnerRuntime; @@ -437,6 +442,7 @@ export const createRuntimeAPI = ({ const runtimeInstance: RunnerRuntime = new RunnerRuntime({ testPath, testTimeout, + hookTimeout, }); const createTestAPI = ( diff --git a/packages/core/src/runtime/worker/env/happyDom.ts b/packages/core/src/runtime/worker/env/happyDom.ts new file mode 100644 index 00000000..16071867 --- /dev/null +++ b/packages/core/src/runtime/worker/env/happyDom.ts @@ -0,0 +1,40 @@ +import type { Window as HappyDOMWindow } from 'happy-dom'; +import type { TestEnvironment } from '../../../types'; +import { addDefaultErrorHandler, installGlobal } from './utils'; + +type HappyDOMOptions = ConstructorParameters[0]; + +export const environment = < + TestEnvironment +>{ + name: 'happy-dom', + async setup(global, { happyDom = {} }) { + const { Window } = await import('happy-dom'); + + const win = new Window({ + ...happyDom, + url: happyDom.url || 'http://localhost:3000', + console: console && global.console ? global.console : undefined, + }); + + const cleanupGlobal = installGlobal(global, win, { + // jsdom doesn't support Request and Response, but happy-dom does + additionalKeys: ['Request', 'Response', 'MessagePort', 'fetch'], + }); + + const cleanupHandler = addDefaultErrorHandler(global as unknown as Window); + + return { + async teardown() { + cleanupHandler(); + if (win.close && win.happyDOM.abort) { + await win.happyDOM.abort(); + win.close(); + } else { + await win.happyDOM.cancelAsync(); + } + cleanupGlobal(); + }, + }; + }, +}; diff --git a/packages/core/src/runtime/worker/env/jsdom.ts b/packages/core/src/runtime/worker/env/jsdom.ts index 6488ccb5..1b6d314d 100644 --- a/packages/core/src/runtime/worker/env/jsdom.ts +++ b/packages/core/src/runtime/worker/env/jsdom.ts @@ -1,113 +1,12 @@ import type { ConstructorOptions } from 'jsdom'; import type { TestEnvironment } from '../../../types'; -import { getWindowKeys, SKIP_KEYS } from './jsdomKeys'; +import { addDefaultErrorHandler, installGlobal } from './utils'; type JSDOMOptions = ConstructorOptions & { html?: string | ArrayBufferLike; console?: boolean; }; -function installGlobal( - global: any, - win: any, - options: { - additionalKeys?: string[]; - } = {}, -): () => void { - const keys = getWindowKeys(global, win, options.additionalKeys); - const isClassLike = (name: string) => { - return name[0] === name[0]?.toUpperCase(); - }; - - const originals = new Map(); - - const overrides = new Map(); - for (const key of keys) { - const boundFunction = - typeof win[key] === 'function' && !isClassLike(key) - ? win[key].bind(win) - : undefined; - - if (key in global) { - originals.set(key, global[key]); - } - - Object.defineProperty(global, key, { - get() { - if (overrides.has(key)) { - return overrides.get(key); - } - if (boundFunction) { - return boundFunction; - } - return win[key]; - }, - set(v) { - overrides.set(key, v); - }, - configurable: true, - }); - } - - global.window = global; - global.self = global; - global.top = global; - global.parent = global; - - if (global.global) { - global.global = global; - } - - // rewrite defaultView to reference the same global context - if (global.document?.defaultView) { - Object.defineProperty(global.document, 'defaultView', { - get: () => global, - enumerable: true, - configurable: true, - }); - } - - for (const k of SKIP_KEYS) { - keys.add(k); - } - - return () => { - for (const key of keys) { - delete global[key]; - } - originals.forEach((v, k) => { - global[k] = v; - }); - }; -} - -function addDefaultErrorHandler(window: Window) { - let userErrorListenerCount = 0; - const throwUnhandledError = (e: ErrorEvent) => { - if (userErrorListenerCount === 0 && e.error != null) { - process.emit('uncaughtException', e.error); - } - }; - const addEventListener = window.addEventListener.bind(window); - const removeEventListener = window.removeEventListener.bind(window); - window.addEventListener('error', throwUnhandledError); - window.addEventListener = function (...args: [any, any, any]) { - if (args[0] === 'error') { - userErrorListenerCount++; - } - return addEventListener.apply(this, args); - }; - window.removeEventListener = function (...args: [any, any, any]) { - if (args[0] === 'error' && userErrorListenerCount) { - userErrorListenerCount--; - } - return removeEventListener.apply(this, args); - }; - return () => { - window.removeEventListener('error', throwUnhandledError); - }; -} - export const environment = >{ name: 'jsdom', async setup(global, { jsdom = {} }) { diff --git a/packages/core/src/runtime/worker/env/jsdomKeys.ts b/packages/core/src/runtime/worker/env/jsdomKeys.ts index 7805dc44..b4867c2c 100644 --- a/packages/core/src/runtime/worker/env/jsdomKeys.ts +++ b/packages/core/src/runtime/worker/env/jsdomKeys.ts @@ -268,26 +268,3 @@ const OTHER_KEYS = [ ]; export const KEYS: string[] = LIVING_KEYS.concat(OTHER_KEYS); - -export const SKIP_KEYS: string[] = ['window', 'self', 'top', 'parent']; - -export function getWindowKeys( - global: any, - win: any, - additionalKeys: string[] = [], -): Set { - const keysArray = [...additionalKeys, ...KEYS]; - - return new Set( - keysArray.concat(Object.getOwnPropertyNames(win)).filter((k) => { - if (SKIP_KEYS.includes(k)) { - return false; - } - if (k in global) { - return keysArray.includes(k); - } - - return true; - }), - ); -} diff --git a/packages/core/src/runtime/worker/env/utils.ts b/packages/core/src/runtime/worker/env/utils.ts new file mode 100644 index 00000000..88f11706 --- /dev/null +++ b/packages/core/src/runtime/worker/env/utils.ts @@ -0,0 +1,132 @@ +import { KEYS } from './jsdomKeys'; + +export const SKIP_KEYS: string[] = ['window', 'self', 'top', 'parent']; + +export function getWindowKeys( + global: any, + win: any, + additionalKeys: string[] = [], +): Set { + const keysArray = [...additionalKeys, ...KEYS]; + + return new Set( + keysArray.concat(Object.getOwnPropertyNames(win)).filter((k) => { + if (SKIP_KEYS.includes(k)) { + return false; + } + if (k in global) { + return keysArray.includes(k); + } + + return true; + }), + ); +} + +function isClassLike(name: string) { + return name[0] === name[0]?.toUpperCase(); +} + +export function installGlobal( + global: any, + win: any, + options: { + /** + * @default true + */ + bindFunctions?: boolean; + additionalKeys?: string[]; + } = {}, +): () => void { + const { bindFunctions = true } = options || {}; + const keys = getWindowKeys(global, win, options.additionalKeys); + + const originals = new Map(); + + const overrides = new Map(); + for (const key of keys) { + const boundFunction = + bindFunctions && + typeof win[key] === 'function' && + !isClassLike(key) && + win[key].bind(win); + + if (key in global) { + originals.set(key, global[key]); + } + + Object.defineProperty(global, key, { + get() { + if (overrides.has(key)) { + return overrides.get(key); + } + if (boundFunction) { + return boundFunction; + } + return win[key]; + }, + set(v) { + overrides.set(key, v); + }, + configurable: true, + }); + } + + global.window = global; + global.self = global; + global.top = global; + global.parent = global; + + if (global.global) { + global.global = global; + } + + // rewrite defaultView to reference the same global context + if (global.document?.defaultView) { + Object.defineProperty(global.document, 'defaultView', { + get: () => global, + enumerable: true, + configurable: true, + }); + } + + for (const k of SKIP_KEYS) { + keys.add(k); + } + + return () => { + for (const key of keys) { + delete global[key]; + } + originals.forEach((v, k) => { + global[k] = v; + }); + }; +} + +export function addDefaultErrorHandler(window: Window) { + let userErrorListenerCount = 0; + const throwUnhandledError = (e: ErrorEvent) => { + if (userErrorListenerCount === 0 && e.error != null) { + process.emit('uncaughtException', e.error); + } + }; + const addEventListener = window.addEventListener.bind(window); + const removeEventListener = window.removeEventListener.bind(window); + window.addEventListener('error', throwUnhandledError); + window.addEventListener = function (...args: [any, any, any]) { + if (args[0] === 'error') { + userErrorListenerCount++; + } + return addEventListener.apply(this, args); + }; + window.removeEventListener = function (...args: [any, any, any]) { + if (args[0] === 'error' && userErrorListenerCount) { + userErrorListenerCount--; + } + return removeEventListener.apply(this, args); + }; + return (): void => { + window.removeEventListener('error', throwUnhandledError); + }; +} diff --git a/packages/core/src/runtime/worker/index.ts b/packages/core/src/runtime/worker/index.ts index 6b47d0b1..8f1cc68f 100644 --- a/packages/core/src/runtime/worker/index.ts +++ b/packages/core/src/runtime/worker/index.ts @@ -8,7 +8,7 @@ import type { } from '../../types'; import './setup'; import { globalApis } from '../../utils/constants'; -import { undoSerializableConfig } from '../../utils/helper'; +import { color, undoSerializableConfig } from '../../utils/helper'; import { formatTestError } from '../util'; import { loadModule } from './loadModule'; import { createForksRpcOptions, createRuntimeRpc } from './rpc'; @@ -23,6 +23,9 @@ const getGlobalApi = (api: Rstest) => { }, {}); }; +const listeners: (() => void)[] = []; +let isTeardown = false; + const preparePool = async ({ entryInfo: { distPath, testPath }, sourceMaps, @@ -83,12 +86,21 @@ const preparePool = async ({ }, }); + // Reset listeners only when preparePool is called again (running without isolation) + listeners.forEach((fn) => fn()); + listeners.length = 0; + const unhandledErrors: Error[] = []; const handleError = (e: Error, type: string) => { e.name = type; - console.error(e); - unhandledErrors.push(e); + if (isTeardown) { + e.stack = `${color.yellow('Caught error after test environment was torn down:')}\n\n${e.stack}`; + console.error(e); + } else { + console.error(e); + unhandledErrors.push(e); + } }; const uncaughtException = (e: Error) => handleError(e, 'uncaughtException'); @@ -97,7 +109,7 @@ const preparePool = async ({ process.on('uncaughtException', uncaughtException); process.on('unhandledRejection', unhandledRejection); - cleanupFns.push(() => { + listeners.push(() => { process.off('uncaughtException', uncaughtException); process.off('unhandledRejection', unhandledRejection); }); @@ -108,6 +120,10 @@ const preparePool = async ({ const { environment } = await import('./env/jsdom'); const { teardown } = await environment.setup(global, {}); cleanupFns.push(() => teardown(global)); + } else if (testEnvironment === 'happy-dom') { + const { environment } = await import('./env/happyDom'); + const { teardown } = await environment.setup(global, {}); + cleanupFns.push(async () => await teardown(global)); } const rstestContext = { @@ -197,6 +213,7 @@ const runInPool = async ( } | TestFileResult > => { + isTeardown = false; const { entryInfo: { distPath, testPath }, setupEntries, @@ -209,6 +226,15 @@ const runInPool = async ( const cleanups: (() => MaybePromise)[] = []; + const exit = process.exit; + process.exit = (code = process.exitCode || 0): never => { + throw new Error(`process.exit unexpectedly called with "${code}"`); + }; + + cleanups.push(() => { + process.exit = exit; + }); + if (type === 'collect') { try { const { @@ -244,14 +270,11 @@ const runInPool = async ( }; } finally { await Promise.all(cleanups.map((fn) => fn())); + isTeardown = true; } } - const exit = process.exit; try { - process.exit = (code = process.exitCode || 0): never => { - throw new Error(`process.exit unexpectedly called with "${code}"`); - }; const { rstestContext, runner, @@ -307,7 +330,7 @@ const runInPool = async ( }; } finally { await Promise.all(cleanups.map((fn) => fn())); - process.exit = exit; + isTeardown = true; } }; diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index 07437e64..3405ddfb 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -87,7 +87,7 @@ export interface RstestConfig { * * @default 'node' */ - testEnvironment?: 'node' | 'jsdom'; + testEnvironment?: 'node' | 'jsdom' | 'happy-dom'; /** * print console traces when calling any console method. @@ -133,6 +133,12 @@ export interface RstestConfig { */ testTimeout?: number; + /** + * Timeout of hook in milliseconds. + * @default 10000 + */ + hookTimeout?: number; + /** * Automatically clear mock calls, instances, contexts and results before every test. * @default false diff --git a/packages/core/src/types/environment.ts b/packages/core/src/types/environment.ts index d953a044..68840cfc 100644 --- a/packages/core/src/types/environment.ts +++ b/packages/core/src/types/environment.ts @@ -4,10 +4,10 @@ export interface TestEnvironmentReturn { teardown: (global: any) => MaybePromise; } -export interface TestEnvironment { +export interface TestEnvironment> { name: string; setup: ( global: Global, - options: Record, + options: Options, ) => MaybePromise; } diff --git a/packages/core/src/types/worker.ts b/packages/core/src/types/worker.ts index 0baed83c..0441e21c 100644 --- a/packages/core/src/types/worker.ts +++ b/packages/core/src/types/worker.ts @@ -45,6 +45,7 @@ export type RuntimeConfig = Pick< | 'disableConsoleIntercept' | 'testEnvironment' | 'isolate' + | 'hookTimeout' >; export type WorkerContext = { diff --git a/packages/core/src/utils/helper.ts b/packages/core/src/utils/helper.ts index 84d98a13..bc7784ae 100644 --- a/packages/core/src/utils/helper.ts +++ b/packages/core/src/utils/helper.ts @@ -54,30 +54,36 @@ export function formatError(error: unknown): Error | string { return String(error); } -export const prettyTime = ( - milliseconds: number, - shouldFormat = true, -): string => { - const format = (time: string) => (shouldFormat ? color.bold(time) : time); - const indent = shouldFormat ? ' ' : ''; - +export const prettyTime = (milliseconds: number): string => { if (milliseconds < 1000) { - return `${Math.round(milliseconds)}${indent}ms`; + return `${Math.round(milliseconds)}ms`; } const seconds = milliseconds / 1000; - if (seconds < 10) { - const digits = seconds >= 0.01 ? 2 : 3; - return `${format(seconds.toFixed(digits))}${indent}s`; - } + const getSecond = (seconds: number, needDigits?: boolean) => { + if (!needDigits || seconds === Math.ceil(seconds)) { + return `${Math.round(seconds).toString()}s`; + } + const digits = seconds < 10 ? (seconds >= 0.01 ? 2 : 3) : 1; + return `${seconds.toFixed(digits)}s`; + }; - if (seconds < 60) { - return `${format(seconds.toFixed(1))}${indent}s`; + const minutes = Math.floor(seconds / 60); + const secondsRemainder = seconds % 60; + let time = ''; + + if (minutes > 0) { + time += `${minutes}m`; } - const minutes = seconds / 60; - return `${format(minutes.toFixed(2))}${indent}m`; + if (secondsRemainder > 0) { + if (minutes > 0) { + time += ' '; + } + time += getSecond(secondsRemainder, !minutes); + } + return time; }; const getTaskNames = ( diff --git a/packages/core/tests/utils/helper.test.ts b/packages/core/tests/utils/helper.test.ts index 7187e2a2..135c5bd2 100644 --- a/packages/core/tests/utils/helper.test.ts +++ b/packages/core/tests/utils/helper.test.ts @@ -1,5 +1,5 @@ import { sep } from 'node:path'; -import { parsePosix } from '../../src/utils/helper'; +import { parsePosix, prettyTime } from '../../src/utils/helper'; it('parsePosix correctly', () => { const splitPaths = ['packages', 'core', 'tests', 'index.test.ts']; @@ -9,3 +9,15 @@ it('parsePosix correctly', () => { base: 'index.test.ts', }); }); + +it('should prettyTime correctly', () => { + expect(prettyTime(100)).toBe('100ms'); + expect(prettyTime(1000)).toBe('1s'); + expect(prettyTime(1500)).toBe('1.50s'); + expect(prettyTime(2000)).toBe('2s'); + expect(prettyTime(3000)).toBe('3s'); + expect(prettyTime(60000)).toBe('1m'); + expect(prettyTime(110000)).toBe('1m 50s'); + expect(prettyTime(111100)).toBe('1m 51s'); + expect(prettyTime(111900)).toBe('1m 52s'); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9763cd3..1c4a24ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,6 +172,9 @@ importers: cac: specifier: ^6.7.14 version: 6.7.14 + happy-dom: + specifier: ^18.0.1 + version: 18.0.1 jest-diff: specifier: ^30.0.3 version: 30.0.3 @@ -257,40 +260,7 @@ importers: specifier: workspace:* version: link:../../packages/core - tests/externals: - devDependencies: - '@rstest/core': - specifier: workspace:* - version: link:../../packages/core - picocolors: - specifier: ^1.1.1 - version: 1.1.1 - strip-ansi: - specifier: ^7.1.0 - version: 7.1.0 - - tests/externals/fixtures/test-interop: {} - - tests/externals/fixtures/test-lodash: {} - - tests/externals/fixtures/test-module-field: {} - - tests/externals/fixtures/test-pkg: - devDependencies: - pathe: - specifier: ^2.0.3 - version: 2.0.3 - - tests/globals/fixtures: - devDependencies: - '@rstest/core': - specifier: workspace:* - version: link:../../../packages/core - '@rstest/tsconfig': - specifier: workspace:* - version: link:../../../scripts/tsconfig - - tests/jsdom/fixtures: + tests/dom/fixtures: dependencies: react: specifier: ^19.1.0 @@ -320,6 +290,9 @@ importers: '@types/react-dom': specifier: ^19.1.6 version: 19.1.6(@types/react@19.1.8) + happy-dom: + specifier: ^18.0.1 + version: 18.0.1 jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -327,6 +300,39 @@ importers: specifier: ^5.8.3 version: 5.8.3 + tests/externals: + devDependencies: + '@rstest/core': + specifier: workspace:* + version: link:../../packages/core + picocolors: + specifier: ^1.1.1 + version: 1.1.1 + strip-ansi: + specifier: ^7.1.0 + version: 7.1.0 + + tests/externals/fixtures/test-interop: {} + + tests/externals/fixtures/test-lodash: {} + + tests/externals/fixtures/test-module-field: {} + + tests/externals/fixtures/test-pkg: + devDependencies: + pathe: + specifier: ^2.0.3 + version: 2.0.3 + + tests/globals/fixtures: + devDependencies: + '@rstest/core': + specifier: workspace:* + version: link:../../../packages/core + '@rstest/tsconfig': + specifier: workspace:* + version: link:../../../scripts/tsconfig + tests/lifecycle: devDependencies: '@rstest/core': @@ -1254,6 +1260,9 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@20.19.4': + resolution: {integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==} + '@types/node@22.13.8': resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==} @@ -1289,6 +1298,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2116,6 +2128,10 @@ packages: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} + happy-dom@18.0.1: + resolution: {integrity: sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==} + engines: {node: '>=20.0.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3701,6 +3717,9 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unhead@2.0.10: resolution: {integrity: sha512-GT188rzTCeSKt55tYyQlHHKfUTtZvgubrXiwzGeXg6UjcKO3FsagaMzQp6TVDrpDY++3i7Qt0t3pnCc/ebg5yQ==} @@ -3809,6 +3828,10 @@ packages: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} @@ -5007,6 +5030,10 @@ snapshots: '@types/node@12.20.55': {} + '@types/node@20.19.4': + dependencies: + undici-types: 6.21.0 + '@types/node@22.13.8': dependencies: undici-types: 6.20.0 @@ -5041,6 +5068,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/whatwg-mimetype@3.0.2': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': @@ -5870,6 +5899,12 @@ snapshots: dependencies: duplexer: 0.1.2 + happy-dom@18.0.1: + dependencies: + '@types/node': 20.19.4 + '@types/whatwg-mimetype': 3.0.2 + whatwg-mimetype: 3.0.0 + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -7790,6 +7825,8 @@ snapshots: undici-types@6.20.0: {} + undici-types@6.21.0: {} + unhead@2.0.10: dependencies: hookable: 5.5.3 @@ -7916,6 +7953,8 @@ snapshots: dependencies: iconv-lite: 0.6.3 + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} whatwg-url@14.2.0: diff --git a/tests/dom/css.test.ts b/tests/dom/css.test.ts new file mode 100644 index 00000000..fec6469a --- /dev/null +++ b/tests/dom/css.test.ts @@ -0,0 +1,18 @@ +import { describe, it } from '@rstest/core'; +import { runCli } from './utils'; + +const filters = 'test/css'; + +describe('jsdom', () => { + it('should run css test correctly', async () => { + const { expectExecSuccess } = await runCli(filters, 'jsdom'); + await expectExecSuccess(); + }); +}); + +describe('happy-dom', () => { + it('should run css test correctly', async () => { + const { expectExecSuccess } = await runCli(filters, 'happy-dom'); + await expectExecSuccess(); + }); +}); diff --git a/tests/jsdom/fixtures/package.json b/tests/dom/fixtures/package.json similarity index 95% rename from tests/jsdom/fixtures/package.json rename to tests/dom/fixtures/package.json index a3740255..4dbc42cf 100644 --- a/tests/jsdom/fixtures/package.json +++ b/tests/dom/fixtures/package.json @@ -18,6 +18,7 @@ "@testing-library/react": "^16.3.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", + "happy-dom": "^18.0.1", "jsdom": "^26.1.0", "typescript": "^5.8.3" } diff --git a/tests/jsdom/fixtures/rsbuild.config.ts b/tests/dom/fixtures/rsbuild.config.ts similarity index 100% rename from tests/jsdom/fixtures/rsbuild.config.ts rename to tests/dom/fixtures/rsbuild.config.ts diff --git a/tests/jsdom/fixtures/rstest.config.ts b/tests/dom/fixtures/rstest.config.ts similarity index 88% rename from tests/jsdom/fixtures/rstest.config.ts rename to tests/dom/fixtures/rstest.config.ts index 0237e2ae..bb742cc6 100644 --- a/tests/jsdom/fixtures/rstest.config.ts +++ b/tests/dom/fixtures/rstest.config.ts @@ -3,6 +3,5 @@ import rsbuildConfig from './rsbuild.config'; export default defineConfig({ ...(rsbuildConfig as RstestConfig), - testEnvironment: 'jsdom', setupFiles: ['./test/setup.ts'], }); diff --git a/tests/jsdom/fixtures/rstest.externals.config.ts b/tests/dom/fixtures/rstest.externals.config.ts similarity index 90% rename from tests/jsdom/fixtures/rstest.externals.config.ts rename to tests/dom/fixtures/rstest.externals.config.ts index 7c861613..adbfb5e1 100644 --- a/tests/jsdom/fixtures/rstest.externals.config.ts +++ b/tests/dom/fixtures/rstest.externals.config.ts @@ -3,7 +3,6 @@ import rsbuildConfig from './rsbuild.config'; export default defineConfig({ ...(rsbuildConfig as RstestConfig), - testEnvironment: 'jsdom', setupFiles: ['./test/setup.ts'], output: { externals: [/react/], diff --git a/tests/jsdom/fixtures/src/App.css b/tests/dom/fixtures/src/App.css similarity index 100% rename from tests/jsdom/fixtures/src/App.css rename to tests/dom/fixtures/src/App.css diff --git a/tests/jsdom/fixtures/src/App.module.css b/tests/dom/fixtures/src/App.module.css similarity index 100% rename from tests/jsdom/fixtures/src/App.module.css rename to tests/dom/fixtures/src/App.module.css diff --git a/tests/jsdom/fixtures/src/App.tsx b/tests/dom/fixtures/src/App.tsx similarity index 100% rename from tests/jsdom/fixtures/src/App.tsx rename to tests/dom/fixtures/src/App.tsx diff --git a/tests/jsdom/fixtures/src/env.d.ts b/tests/dom/fixtures/src/env.d.ts similarity index 100% rename from tests/jsdom/fixtures/src/env.d.ts rename to tests/dom/fixtures/src/env.d.ts diff --git a/tests/jsdom/fixtures/src/index.tsx b/tests/dom/fixtures/src/index.tsx similarity index 100% rename from tests/jsdom/fixtures/src/index.tsx rename to tests/dom/fixtures/src/index.tsx diff --git a/tests/jsdom/fixtures/test/App.test.tsx b/tests/dom/fixtures/test/App.test.tsx similarity index 100% rename from tests/jsdom/fixtures/test/App.test.tsx rename to tests/dom/fixtures/test/App.test.tsx diff --git a/tests/jsdom/fixtures/test/css.test.tsx b/tests/dom/fixtures/test/css.test.tsx similarity index 100% rename from tests/jsdom/fixtures/test/css.test.tsx rename to tests/dom/fixtures/test/css.test.tsx diff --git a/tests/jsdom/fixtures/test/handledError.test.tsx b/tests/dom/fixtures/test/handledError.test.tsx similarity index 100% rename from tests/jsdom/fixtures/test/handledError.test.tsx rename to tests/dom/fixtures/test/handledError.test.tsx diff --git a/tests/jsdom/fixtures/test/jestDom.test.tsx b/tests/dom/fixtures/test/jestDom.test.tsx similarity index 100% rename from tests/jsdom/fixtures/test/jestDom.test.tsx rename to tests/dom/fixtures/test/jestDom.test.tsx diff --git a/tests/jsdom/fixtures/test/setup.ts b/tests/dom/fixtures/test/setup.ts similarity index 100% rename from tests/jsdom/fixtures/test/setup.ts rename to tests/dom/fixtures/test/setup.ts diff --git a/tests/jsdom/fixtures/test/test.d.ts b/tests/dom/fixtures/test/test.d.ts similarity index 100% rename from tests/jsdom/fixtures/test/test.d.ts rename to tests/dom/fixtures/test/test.d.ts diff --git a/tests/jsdom/fixtures/test/unhandledError.test.tsx b/tests/dom/fixtures/test/unhandledError.test.tsx similarity index 100% rename from tests/jsdom/fixtures/test/unhandledError.test.tsx rename to tests/dom/fixtures/test/unhandledError.test.tsx diff --git a/tests/jsdom/fixtures/tsconfig.json b/tests/dom/fixtures/tsconfig.json similarity index 100% rename from tests/jsdom/fixtures/tsconfig.json rename to tests/dom/fixtures/tsconfig.json diff --git a/tests/dom/handledError.test.ts b/tests/dom/handledError.test.ts new file mode 100644 index 00000000..630d2ea2 --- /dev/null +++ b/tests/dom/handledError.test.ts @@ -0,0 +1,20 @@ +import { describe, it } from '@rstest/core'; +import { runCli } from './utils'; + +const filters = 'test/handledError'; + +describe('jsdom', () => { + it('should handle error correctly', async () => { + const { expectExecSuccess, cli } = await runCli(filters, 'jsdom'); + await cli.exec; + await expectExecSuccess(); + }); +}); + +describe('happy-dom', () => { + it('should handle error correctly', async () => { + const { expectExecSuccess, cli } = await runCli(filters, 'happy-dom'); + await cli.exec; + await expectExecSuccess(); + }); +}); diff --git a/tests/dom/index.test.ts b/tests/dom/index.test.ts new file mode 100644 index 00000000..dd9c0874 --- /dev/null +++ b/tests/dom/index.test.ts @@ -0,0 +1,45 @@ +import { describe, it } from '@rstest/core'; +import { runCli } from './utils'; + +const appFilters = 'test/App'; +const jestDomFilters = 'test/jestDom'; + +const externalConfigArgs = ['--config', 'rstest.externals.config.ts']; + +describe('jsdom', () => { + it('should run test correctly', async () => { + const { expectExecSuccess } = await runCli(appFilters, 'jsdom'); + await expectExecSuccess(); + }); + + it('should run test correctly with custom externals', async () => { + const { expectExecSuccess } = await runCli(appFilters, 'jsdom', { + args: externalConfigArgs, + }); + await expectExecSuccess(); + }); + + it('should run test correctly with jest-dom', async () => { + const { expectExecSuccess } = await runCli(jestDomFilters, 'jsdom'); + await expectExecSuccess(); + }); +}); + +describe('happy-dom', () => { + it('should run test correctly', async () => { + const { expectExecSuccess } = await runCli(appFilters, 'happy-dom'); + await expectExecSuccess(); + }); + + it('should run test correctly with custom externals', async () => { + const { expectExecSuccess } = await runCli(appFilters, 'happy-dom', { + args: externalConfigArgs, + }); + await expectExecSuccess(); + }); + + it('should run test correctly with jest-dom', async () => { + const { expectExecSuccess } = await runCli(jestDomFilters, 'happy-dom'); + await expectExecSuccess(); + }); +}); diff --git a/tests/dom/unhandledError.test.ts b/tests/dom/unhandledError.test.ts new file mode 100644 index 00000000..4e551f0c --- /dev/null +++ b/tests/dom/unhandledError.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from '@rstest/core'; +import { runCli } from './utils'; + +const filters = 'test/unhandledError'; + +describe('jsdom', () => { + it('should catch error correctly', async () => { + const { cli } = await runCli(filters, 'jsdom'); + + await cli.exec; + + expect(cli.exec.process?.exitCode).toBe(1); + }); +}); + +describe('happy-dom', () => { + it('should catch error correctly', async () => { + const { cli } = await runCli(filters, 'happy-dom'); + + await cli.exec; + + expect(cli.exec.process?.exitCode).toBe(1); + }); +}); diff --git a/tests/dom/utils.ts b/tests/dom/utils.ts new file mode 100644 index 00000000..01694919 --- /dev/null +++ b/tests/dom/utils.ts @@ -0,0 +1,30 @@ +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { runRstestCli } from '../scripts'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export const runCli = async ( + _filters: string | string[], + testEnvironment: 'jsdom' | 'happy-dom', + extra?: { + args?: string[]; + }, +) => { + const filters = Array.isArray(_filters) ? _filters : [_filters]; + return await runRstestCli({ + command: 'rstest', + args: [ + 'run', + `--testEnvironment=${testEnvironment}`, + ...(extra?.args || []), + ...filters, + ], + options: { + nodeOptions: { + cwd: join(__dirname, 'fixtures'), + }, + }, + }); +}; diff --git a/tests/jsdom/css.test.ts b/tests/jsdom/css.test.ts deleted file mode 100644 index 64ed4b58..00000000 --- a/tests/jsdom/css.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { it } from '@rstest/core'; -import { runRstestCli } from '../scripts'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -it('should run css test correctly', async () => { - const { expectExecSuccess } = await runRstestCli({ - command: 'rstest', - args: ['run', 'test/css'], - options: { - nodeOptions: { - cwd: join(__dirname, 'fixtures'), - }, - }, - }); - - await expectExecSuccess(); -}); diff --git a/tests/jsdom/handledError.test.ts b/tests/jsdom/handledError.test.ts deleted file mode 100644 index a939c0ca..00000000 --- a/tests/jsdom/handledError.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { it } from '@rstest/core'; -import { runRstestCli } from '../scripts'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -it('should handle error correctly', async () => { - const { cli, expectExecSuccess } = await runRstestCli({ - command: 'rstest', - args: ['run', 'test/handledError'], - options: { - nodeOptions: { - cwd: join(__dirname, 'fixtures'), - }, - }, - }); - - await cli.exec; - - await expectExecSuccess(); -}); diff --git a/tests/jsdom/index.test.ts b/tests/jsdom/index.test.ts deleted file mode 100644 index 9e11c26b..00000000 --- a/tests/jsdom/index.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { it } from '@rstest/core'; -import { runRstestCli } from '../scripts'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -it('should run jsdom test correctly', async () => { - const { expectExecSuccess } = await runRstestCli({ - command: 'rstest', - args: ['run', 'test/App'], - options: { - nodeOptions: { - cwd: join(__dirname, 'fixtures'), - }, - }, - }); - - await expectExecSuccess(); -}); - -it('should run jsdom test correctly with custom externals', async () => { - const { expectExecSuccess } = await runRstestCli({ - command: 'rstest', - args: ['run', 'test/App', '--config', 'rstest.externals.config.ts'], - options: { - nodeOptions: { - cwd: join(__dirname, 'fixtures'), - }, - }, - }); - - await expectExecSuccess(); -}); - -it('should run jsdom test correctly with jest-dom', async () => { - const { expectExecSuccess } = await runRstestCli({ - command: 'rstest', - args: ['run', 'test/jestDom'], - options: { - nodeOptions: { - cwd: join(__dirname, 'fixtures'), - }, - }, - }); - - await expectExecSuccess(); -}); diff --git a/tests/jsdom/unhandledError.test.ts b/tests/jsdom/unhandledError.test.ts deleted file mode 100644 index dacf7cbf..00000000 --- a/tests/jsdom/unhandledError.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { expect, it } from '@rstest/core'; -import { runRstestCli } from '../scripts'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -it('should catch error correctly', async () => { - const { cli } = await runRstestCli({ - command: 'rstest', - args: ['run', 'test/unhandledError'], - options: { - nodeOptions: { - cwd: join(__dirname, 'fixtures'), - }, - }, - }); - - await cli.exec; - - expect(cli.exec.process?.exitCode).toBe(1); -}); diff --git a/website/docs/en/config/test/_meta.json b/website/docs/en/config/test/_meta.json index 26e095bc..53eec04d 100644 --- a/website/docs/en/config/test/_meta.json +++ b/website/docs/en/config/test/_meta.json @@ -7,6 +7,7 @@ "testEnvironment", "testNamePattern", "testTimeout", + "hookTimeout", "retry", "root", "name", diff --git a/website/docs/en/config/test/clearMocks.mdx b/website/docs/en/config/test/clearMocks.mdx index 1cf807d6..515148e4 100644 --- a/website/docs/en/config/test/clearMocks.mdx +++ b/website/docs/en/config/test/clearMocks.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--clearMocks` Automatically clear mock calls, instances, contexts and results before every test. diff --git a/website/docs/en/config/test/disableConsoleIntercept.mdx b/website/docs/en/config/test/disableConsoleIntercept.mdx index e5a07d1a..56d3d2a3 100644 --- a/website/docs/en/config/test/disableConsoleIntercept.mdx +++ b/website/docs/en/config/test/disableConsoleIntercept.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--disableConsoleIntercept` Disable interception of console logs. By default, Rstest intercepts the console log, which will help track log sources. diff --git a/website/docs/en/config/test/exclude.mdx b/website/docs/en/config/test/exclude.mdx index 559fa656..8aa38f91 100644 --- a/website/docs/en/config/test/exclude.mdx +++ b/website/docs/en/config/test/exclude.mdx @@ -6,6 +6,7 @@ overviewHeaders: [] - **Type:** `string[]` - **Default:** `['**/node_modules/**', '**/dist/**', '**/.{idea,git,cache,output,temp}/**']` +- **CLI:** `--exclude "**/node_modules/**"` A list of glob patterns that should be excluded from your test files. diff --git a/website/docs/en/config/test/globals.mdx b/website/docs/en/config/test/globals.mdx index 027121b9..5d74b434 100644 --- a/website/docs/en/config/test/globals.mdx +++ b/website/docs/en/config/test/globals.mdx @@ -6,6 +6,7 @@ overviewHeaders: [] - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--globals` Provide global Rstest APIs for test files, such as `expect`, `test`, `describe`, etc. diff --git a/website/docs/en/config/test/hookTimeout.mdx b/website/docs/en/config/test/hookTimeout.mdx new file mode 100644 index 00000000..f09f3021 --- /dev/null +++ b/website/docs/en/config/test/hookTimeout.mdx @@ -0,0 +1,7 @@ +# hookTimeout + +- **Type:** `number` +- **Default:** `10_000` +- **CLI:** `--hookTimeout=10000` + +Default timeout of [hook](/api/test-api/hooks) in milliseconds. `0` will disable the timeout. diff --git a/website/docs/en/config/test/include.mdx b/website/docs/en/config/test/include.mdx index 9534f78b..7f9b16d3 100644 --- a/website/docs/en/config/test/include.mdx +++ b/website/docs/en/config/test/include.mdx @@ -2,6 +2,7 @@ - **Type:** `string[]` - **Default:** `['**/*.{test,spec}.?(c|m)[jt]s?(x)']` +- **CLI:** `rstest **/*.test.ts`, `rstest index.test.ts` A list of glob patterns that match your test files. diff --git a/website/docs/en/config/test/isolate.mdx b/website/docs/en/config/test/isolate.mdx index 61cbf282..e3e3e8ce 100644 --- a/website/docs/en/config/test/isolate.mdx +++ b/website/docs/en/config/test/isolate.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `true` +- **CLI:** `--isolate=false` Run tests in an isolated environment. diff --git a/website/docs/en/config/test/maxConcurrency.mdx b/website/docs/en/config/test/maxConcurrency.mdx index 4df56838..3f339efc 100644 --- a/website/docs/en/config/test/maxConcurrency.mdx +++ b/website/docs/en/config/test/maxConcurrency.mdx @@ -2,6 +2,7 @@ - **Type:** `number` - **Default:** `5` +- **CLI:** `--maxConcurrency=10` A number limiting the number of test cases that are allowed to run at the same time when using `test.concurrent` or wrapped in `describe.concurrent`. diff --git a/website/docs/en/config/test/passWithNoTests.mdx b/website/docs/en/config/test/passWithNoTests.mdx index d4aa0d98..76b47324 100644 --- a/website/docs/en/config/test/passWithNoTests.mdx +++ b/website/docs/en/config/test/passWithNoTests.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--passWithNoTests` Pass when no tests are found. diff --git a/website/docs/en/config/test/printConsoleTrace.mdx b/website/docs/en/config/test/printConsoleTrace.mdx index 7bdaea78..12157f98 100644 --- a/website/docs/en/config/test/printConsoleTrace.mdx +++ b/website/docs/en/config/test/printConsoleTrace.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--printConsoleTrace` Print console traces when calling any console method, which is helpful for debugging. diff --git a/website/docs/en/config/test/resetMocks.mdx b/website/docs/en/config/test/resetMocks.mdx index eaf08d4a..cd10ca61 100644 --- a/website/docs/en/config/test/resetMocks.mdx +++ b/website/docs/en/config/test/resetMocks.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--resetMocks` Automatically reset mock state before every test. diff --git a/website/docs/en/config/test/restoreMocks.mdx b/website/docs/en/config/test/restoreMocks.mdx index dffd7a0f..3e500d45 100644 --- a/website/docs/en/config/test/restoreMocks.mdx +++ b/website/docs/en/config/test/restoreMocks.mdx @@ -2,6 +2,7 @@ - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--restoreMocks` Automatically reset mock state before every test. diff --git a/website/docs/en/config/test/retry.mdx b/website/docs/en/config/test/retry.mdx index 4eb10db3..0c2e522c 100644 --- a/website/docs/en/config/test/retry.mdx +++ b/website/docs/en/config/test/retry.mdx @@ -1,9 +1,18 @@ +--- +overviewHeaders: [] +--- + # retry - **Type:** `number` - **Default:** `0` +- **CLI:** `--retry ` + +Retry the test specific number of times if it fails. This is useful for some flaky or non-deterministic test failures. -Retry the test specific number of times if it fails. +## Example + +You can get two retries by setting `retry:2` when the test fails: ```ts title="rstest.config.ts" import { defineConfig } from '@rstest/core'; @@ -12,3 +21,32 @@ export default defineConfig({ retry: 2, }); ``` + +When the test has retried, you may get the following logs: + +- success: + +```bash + ✓ retry.test.ts (1) + ✓ should run success with retry (6ms) (retry x2) + + Test Files 1 passed + Tests 1 passed + Duration 146 ms (build 22 ms, tests 124 ms) +``` + +- or failure: + +```bash + ✗ retry.test.ts (1) + ✗ should run success with retry (6ms) (retry x2) + expected 1 to be 5 // Object.is equality + expected 2 to be 5 // Object.is equality + expected 3 to be 5 // Object.is equality + + ... + + Test Files 1 failed + Tests 1 failed + Duration 171 ms (build 23 ms, tests 148 ms) +``` diff --git a/website/docs/en/config/test/root.mdx b/website/docs/en/config/test/root.mdx index 459da464..5d4d0c1b 100644 --- a/website/docs/en/config/test/root.mdx +++ b/website/docs/en/config/test/root.mdx @@ -2,5 +2,6 @@ - **Type:** `string` - **Default:** [process.cwd()](https://nodejs.org/api/process.html#processcwd) +- **CLI:** `-r=`, `--root=` Specify the project root directory. Can be an absolute path, or a path relative to `process.cwd()`. diff --git a/website/docs/en/config/test/slowTestThreshold.mdx b/website/docs/en/config/test/slowTestThreshold.mdx index f9c181d3..345ac3f4 100644 --- a/website/docs/en/config/test/slowTestThreshold.mdx +++ b/website/docs/en/config/test/slowTestThreshold.mdx @@ -2,5 +2,6 @@ - **Type:** `number` - **Default:** `300` +- **CLI:** `--slowTestThreshold=300` The number of milliseconds after which a test or suite is considered slow and reported as such in the results. diff --git a/website/docs/en/config/test/testEnvironment.mdx b/website/docs/en/config/test/testEnvironment.mdx index 9252dccc..406dd240 100644 --- a/website/docs/en/config/test/testEnvironment.mdx +++ b/website/docs/en/config/test/testEnvironment.mdx @@ -1,11 +1,12 @@ # testEnvironment -- **Type:** `'node' | 'jsdom'` +- **Type:** `'node' | 'jsdom' | 'happy-dom'` - **Default:** `'node'` +- **CLI:** `--testEnvironment=node` The environment that will be used for testing. -The default environment in Rstest is a `Node.js` environment. If you are building a web application, you can use a browser-like environment through `jsdom` instead. +The default environment in Rstest is a `Node.js` environment. If you are building a web application, you can use a browser-like environment through `jsdom` or `happy-dom` instead. ```ts title="rstest.config.ts" import { defineConfig } from '@rstest/core'; @@ -17,7 +18,7 @@ export default defineConfig({ ### Dom testing -Rstest supports [jsdom](https://github.com/jsdom/jsdom) for mocking DOM and browser APIs. +Rstest supports [jsdom](https://github.com/jsdom/jsdom) and [happy-dom](https://github.com/capricorn86/happy-dom) for mocking DOM and browser APIs. If you want to enable DOM testing, you can use the following configuration: @@ -25,14 +26,18 @@ If you want to enable DOM testing, you can use the following configuration: import { defineConfig } from '@rstest/core'; export default defineConfig({ - testEnvironment: 'jsdom', + testEnvironment: 'jsdom', // or 'happy-dom' }); ``` -You also need to install `jsdom`: +You also need to install the corresponding package: ```bash +# For jsdom npm install jsdom -D + +# For happy-dom +npm install happy-dom -D ``` After enabling DOM testing, you can write tests that use browser APIs like `document` and `window`. diff --git a/website/docs/en/config/test/testNamePattern.mdx b/website/docs/en/config/test/testNamePattern.mdx index 5ffe5598..438a2554 100644 --- a/website/docs/en/config/test/testNamePattern.mdx +++ b/website/docs/en/config/test/testNamePattern.mdx @@ -2,6 +2,7 @@ - **Type:** `string | RegExp` - **Default:** `undefined` +- **CLI:** `-t=`, `--testNamePattern=` Run only tests with a name that matches the regex / string. diff --git a/website/docs/en/config/test/testTimeout.mdx b/website/docs/en/config/test/testTimeout.mdx index b6bc5c2a..ecaf5773 100644 --- a/website/docs/en/config/test/testTimeout.mdx +++ b/website/docs/en/config/test/testTimeout.mdx @@ -2,5 +2,6 @@ - **Type:** `number` - **Default:** `5_000` +- **CLI:** `--testTimeout=5000` Default timeout of a test in milliseconds. `0` will disable the timeout. diff --git a/website/docs/en/config/test/unstubEnvs.mdx b/website/docs/en/config/test/unstubEnvs.mdx index 17572f7f..5fbc7075 100644 --- a/website/docs/en/config/test/unstubEnvs.mdx +++ b/website/docs/en/config/test/unstubEnvs.mdx @@ -6,5 +6,6 @@ overviewHeaders: [] - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--unstubEnvs` Restores all `process.env` values that were changed with [rstest.stubEnv](/api/rstest/utilities#rsteststubenv) before every test. diff --git a/website/docs/en/config/test/unstubGlobals.mdx b/website/docs/en/config/test/unstubGlobals.mdx index e585868a..cca1e942 100644 --- a/website/docs/en/config/test/unstubGlobals.mdx +++ b/website/docs/en/config/test/unstubGlobals.mdx @@ -6,5 +6,6 @@ overviewHeaders: [] - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--unstubGlobals` Restores all global variables that were changed with [rstest.stubGlobal](/api/rstest/utilities#rsteststubglobal) before every test. diff --git a/website/docs/en/config/test/update.mdx b/website/docs/en/config/test/update.mdx index f75932cb..741278ba 100644 --- a/website/docs/en/config/test/update.mdx +++ b/website/docs/en/config/test/update.mdx @@ -6,6 +6,7 @@ overviewHeaders: [] - **Type:** `boolean` - **Default:** `false` +- **CLI:** `-u`, `--update` Update snapshot. diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index 38d7dbf5..e17b9624 100644 --- a/website/docs/en/guide/basic/cli.mdx +++ b/website/docs/en/guide/basic/cli.mdx @@ -92,3 +92,34 @@ $ npx rstest list --json $ npx rstest list --json=./output.json ``` + +## CLI options + +Rstest CLI provides several common options that can be used with all commands: + +| Flag | Description | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, see [Specify config file](/guide/basic/configure-rstest#specify-config-file) | +| `--config-loader ` | Specify the config loader, can be `jiti` or `native`, see [Rsbuild - Specify config loader](https://rsbuild.rs/guide/configuration/rsbuild#specify-config-loader) | +| `-r, --root ` | Specify the project root directory, see [root](/config/test/root) | +| `--globals` | Provide global APIs, see [globals](/config/test/globals) | +| `--isolate` | Run tests in an isolated environment, see [isolate](/config/test/isolate) | +| `--exclude ` | Exclude files from test, see [exclude](/config/test/exclude) | +| `-u, --update` | Update snapshot files, see [update](/config/test/update) | +| `--passWithNoTests` | Allows the test suite to pass when no files are found, see [passWithNoTests](/config/test/passWithNoTests) | +| `--printConsoleTrace` | Print console traces when calling any console method, see [printConsoleTrace](/config/test/printConsoleTrace) | +| `--disableConsoleIntercept` | Disable console intercept, see [disableConsoleIntercept](/config/test/disableConsoleIntercept) | +| `--slowTestThreshold ` | The number of milliseconds after which a test or suite is considered slow, see [slowTestThreshold](/config/test/slowTestThreshold) | +| `-t, --testNamePattern ` | Run only tests with a name that matches the regex, see [testNamePattern](/config/test/testNamePattern) | +| `--testEnvironment ` | The environment that will be used for testing, see [testEnvironment](/config/test/testEnvironment) | +| `--testTimeout ` | Timeout of a test in milliseconds, see [testTimeout](/config/test/testTimeout) | +| `--hookTimeout ` | Timeout of hook in milliseconds, see [hookTimeout](/config/test/hookTimeout) | +| `--retry ` | Number of times to retry a test if it fails, see [retry](/config/test/retry) | +| `--maxConcurrency ` | Maximum number of concurrent tests, see [maxConcurrency](/config/test/maxConcurrency) | +| `--clearMocks` | Automatically clear mock calls, instances, contexts and results before every test, see [clearMocks](/config/test/clearMocks) | +| `--resetMocks` | Automatically reset mock state before every test, see [resetMocks](/config/test/resetMocks) | +| `--restoreMocks` | Automatically restore mock state and implementation before every test, see [restoreMocks](/config/test/restoreMocks) | +| `--unstubGlobals` | Restores all global variables that were changed with `rstest.stubGlobal` before every test, see [unstubGlobals](/config/test/unstubGlobals) | +| `--unstubEnvs` | Restores all `process.env` values that were changed with `rstest.stubEnv` before every test, see [unstubEnvs](/config/test/unstubEnvs) | +| `-h, --help` | Display help for command | +| `-v, --version` | Display version | diff --git a/website/docs/zh/config/test/_meta.json b/website/docs/zh/config/test/_meta.json index 26e095bc..53eec04d 100644 --- a/website/docs/zh/config/test/_meta.json +++ b/website/docs/zh/config/test/_meta.json @@ -7,6 +7,7 @@ "testEnvironment", "testNamePattern", "testTimeout", + "hookTimeout", "retry", "root", "name", diff --git a/website/docs/zh/config/test/clearMocks.mdx b/website/docs/zh/config/test/clearMocks.mdx index ae866952..1d1fd72c 100644 --- a/website/docs/zh/config/test/clearMocks.mdx +++ b/website/docs/zh/config/test/clearMocks.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--clearMocks` 清除所有 mock 的 `mock.calls`、`mock.instances`、`mock.contexts` 和 `mock.results` 属性。 diff --git a/website/docs/zh/config/test/disableConsoleIntercept.mdx b/website/docs/zh/config/test/disableConsoleIntercept.mdx index 51fbfe6e..a8458782 100644 --- a/website/docs/zh/config/test/disableConsoleIntercept.mdx +++ b/website/docs/zh/config/test/disableConsoleIntercept.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--disableConsoleIntercept` 禁用 console 日志的拦截。默认情况下,Rstest 会对 console 日志做拦截,这样将有助于追踪日志来源。 diff --git a/website/docs/zh/config/test/exclude.mdx b/website/docs/zh/config/test/exclude.mdx index 1b48f33a..0f437503 100644 --- a/website/docs/zh/config/test/exclude.mdx +++ b/website/docs/zh/config/test/exclude.mdx @@ -6,6 +6,7 @@ overviewHeaders: [] - **类型:** `string[]` - **默认值:** `['**/node_modules/**', '**/dist/**', '**/.{idea,git,cache,output,temp}/**']` +- **CLI:** `--exclude "**/node_modules/**"` 匹配 glob 规则的文件将不会被视为测试文件。 diff --git a/website/docs/zh/config/test/globals.mdx b/website/docs/zh/config/test/globals.mdx index e47e90f8..3d81ae4b 100644 --- a/website/docs/zh/config/test/globals.mdx +++ b/website/docs/zh/config/test/globals.mdx @@ -6,6 +6,7 @@ overviewHeaders: [] - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--globals` 为测试文件提供全局的 Rstest API,如 `expect`、`test`、`describe` 等。 diff --git a/website/docs/zh/config/test/hookTimeout.mdx b/website/docs/zh/config/test/hookTimeout.mdx new file mode 100644 index 00000000..8cc3bad2 --- /dev/null +++ b/website/docs/zh/config/test/hookTimeout.mdx @@ -0,0 +1,7 @@ +# hookTimeout + +- **类型:** `number` +- **默认值:** `10_000` +- **CLI:** `--hookTimeout=10000` + +单个测试 [hook](/api/test-api/hooks) 的超时时间(毫秒)。设置为 `0` 禁用超时。 diff --git a/website/docs/zh/config/test/include.mdx b/website/docs/zh/config/test/include.mdx index 09088bf3..5d100e83 100644 --- a/website/docs/zh/config/test/include.mdx +++ b/website/docs/zh/config/test/include.mdx @@ -2,6 +2,7 @@ - **类型:** `string[]` - **默认值:** `['**/*.{test,spec}.?(c|m)[jt]s?(x)']` +- **CLI:** `rstest **/*.test.ts`, `rstest index.test.ts` 匹配 glob 规则的文件将被视为测试文件。 diff --git a/website/docs/zh/config/test/isolate.mdx b/website/docs/zh/config/test/isolate.mdx index 92aa1daf..f448ed68 100644 --- a/website/docs/zh/config/test/isolate.mdx +++ b/website/docs/zh/config/test/isolate.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `true` +- **CLI:** `--isolate=false` 是否运行每个测试在一个独立的环境。 diff --git a/website/docs/zh/config/test/maxConcurrency.mdx b/website/docs/zh/config/test/maxConcurrency.mdx index 85768bb5..bc918f66 100644 --- a/website/docs/zh/config/test/maxConcurrency.mdx +++ b/website/docs/zh/config/test/maxConcurrency.mdx @@ -2,6 +2,7 @@ - **类型:** `number` - **默认值:** `5` +- **CLI:** `--maxConcurrency=10` 使用 `test.concurrent` 或 `describe.concurrent` 时允许同时运行的最大测试用例数量。 diff --git a/website/docs/zh/config/test/passWithNoTests.mdx b/website/docs/zh/config/test/passWithNoTests.mdx index 7aca78e0..fbcba97c 100644 --- a/website/docs/zh/config/test/passWithNoTests.mdx +++ b/website/docs/zh/config/test/passWithNoTests.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--passWithNoTests` 标记测试通过当未找到测试用例时。 diff --git a/website/docs/zh/config/test/printConsoleTrace.mdx b/website/docs/zh/config/test/printConsoleTrace.mdx index a70ae152..76af3c40 100644 --- a/website/docs/zh/config/test/printConsoleTrace.mdx +++ b/website/docs/zh/config/test/printConsoleTrace.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--printConsoleTrace` console 方法调用时始终打印调用栈,这将有助于调试。 diff --git a/website/docs/zh/config/test/resetMocks.mdx b/website/docs/zh/config/test/resetMocks.mdx index ee4f2551..8788dd90 100644 --- a/website/docs/zh/config/test/resetMocks.mdx +++ b/website/docs/zh/config/test/resetMocks.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--resetMocks` 清除所有 mock 属性,并将每个 mock 的实现重置为其原始实现。 diff --git a/website/docs/zh/config/test/restoreMocks.mdx b/website/docs/zh/config/test/restoreMocks.mdx index b384d6ed..5668059a 100644 --- a/website/docs/zh/config/test/restoreMocks.mdx +++ b/website/docs/zh/config/test/restoreMocks.mdx @@ -2,6 +2,7 @@ - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--restoreMocks` 重置所有 mock,并恢复被 mock 的对象的原始描述符。 diff --git a/website/docs/zh/config/test/retry.mdx b/website/docs/zh/config/test/retry.mdx index 0ea56815..f45cb03f 100644 --- a/website/docs/zh/config/test/retry.mdx +++ b/website/docs/zh/config/test/retry.mdx @@ -1,9 +1,18 @@ +--- +overviewHeaders: [] +--- + # retry - **类型:** `number` - **默认值:** `0` +- **CLI:** `--retry ` + +如果测试执行失败,则重试特定次数。这对于一些会产生不稳定结果的测试用例很有帮助。 -如果测试执行失败,则重试特定次数。 +## 示例 + +你可以通过设置 `retry:2` 来获得两次重试在测试失败时: ```ts title="rstest.config.ts" import { defineConfig } from '@rstest/core'; @@ -12,3 +21,32 @@ export default defineConfig({ retry: 2, }); ``` + +当测试重试时,Rstest 会打印如下日志: + +- 当测试成功时: + +```bash + ✓ retry.test.ts (1) + ✓ should run success with retry (6ms) (retry x2) + + Test Files 1 passed + Tests 1 passed + Duration 146 ms (build 22 ms, tests 124 ms) +``` + +- 失败时: + +```bash + ✗ retry.test.ts (1) + ✗ should run success with retry (6ms) (retry x2) + expected 1 to be 5 // Object.is equality + expected 2 to be 5 // Object.is equality + expected 3 to be 5 // Object.is equality + + ... + + Test Files 1 failed + Tests 1 failed + Duration 171 ms (build 23 ms, tests 148 ms) +``` diff --git a/website/docs/zh/config/test/root.mdx b/website/docs/zh/config/test/root.mdx index abeeaf1a..5ab4b5e0 100644 --- a/website/docs/zh/config/test/root.mdx +++ b/website/docs/zh/config/test/root.mdx @@ -2,5 +2,6 @@ - **类型:** `string` - **默认值:** [process.cwd()](https://nodejs.org/api/process.html#processcwd) +- **CLI:** `-r=`, `--root=` 指定项目根目录。可以是绝对路径,也可以是相对于 `process.cwd()` 的路径。 diff --git a/website/docs/zh/config/test/slowTestThreshold.mdx b/website/docs/zh/config/test/slowTestThreshold.mdx index 7b787bd6..5bb0ddbb 100644 --- a/website/docs/zh/config/test/slowTestThreshold.mdx +++ b/website/docs/zh/config/test/slowTestThreshold.mdx @@ -2,5 +2,6 @@ - **类型:** `number` - **默认值:** `300` +- **CLI:** `--slowTestThreshold=300` 测试运行时间超过指定毫秒数时,被认为是缓慢的并在报告结果中显示。 diff --git a/website/docs/zh/config/test/testEnvironment.mdx b/website/docs/zh/config/test/testEnvironment.mdx index 2c693ee6..302adeef 100644 --- a/website/docs/zh/config/test/testEnvironment.mdx +++ b/website/docs/zh/config/test/testEnvironment.mdx @@ -1,11 +1,12 @@ # testEnvironment -- **类型:** `'node' | 'jsdom'` +- **类型:** `'node' | 'jsdom' | 'happy-dom'` - **默认值:** `'node'` +- **CLI:** `--testEnvironment=node` 测试时所使用的环境。 -Rstest 默认使用 Node.js 作为测试环境。如果你在开发 Web 应用,可以使用类浏览器环境,如 `jsdom`。 +Rstest 默认使用 Node.js 作为测试环境。如果你在开发 Web 应用,可以使用类浏览器环境,如 `jsdom` 或 `happy-dom`。 ```ts title="rstest.config.ts" import { defineConfig } from '@rstest/core'; @@ -17,7 +18,7 @@ export default defineConfig({ ### DOM 测试 -Rstest 支持使用 [jsdom](https://github.com/jsdom/jsdom) 来模拟 DOM 和浏览器 API。 +Rstest 支持使用 [jsdom](https://github.com/jsdom/jsdom) 和 [happy-dom](https://github.com/capricorn86/happy-dom) 来模拟 DOM 和浏览器 API。 如果你想启用 DOM 测试,可以使用如下配置: @@ -25,14 +26,18 @@ Rstest 支持使用 [jsdom](https://github.com/jsdom/jsdom) 来模拟 DOM 和浏 import { defineConfig } from '@rstest/core'; export default defineConfig({ - testEnvironment: 'jsdom', + testEnvironment: 'jsdom', // 或 'happy-dom' }); ``` -你还需要安装 `jsdom`: +你还需要安装对应的包: ```bash +# 使用 jsdom npm install jsdom -D + +# 使用 happy-dom +npm install happy-dom -D ``` 启用 DOM 测试后,你可以在测试用例中使用 `document` 和 `window` 等浏览器 API。 diff --git a/website/docs/zh/config/test/testNamePattern.mdx b/website/docs/zh/config/test/testNamePattern.mdx index aa1a29d9..d964213d 100644 --- a/website/docs/zh/config/test/testNamePattern.mdx +++ b/website/docs/zh/config/test/testNamePattern.mdx @@ -2,6 +2,7 @@ - **类型:** `string | RegExp` - **默认值:** `undefined` +- **CLI:** `-t=`, `--testNamePattern=` 仅运行测试名称中匹配正则表达式或字符串的测试。 diff --git a/website/docs/zh/config/test/testTimeout.mdx b/website/docs/zh/config/test/testTimeout.mdx index ca8e445a..338f89e1 100644 --- a/website/docs/zh/config/test/testTimeout.mdx +++ b/website/docs/zh/config/test/testTimeout.mdx @@ -2,5 +2,6 @@ - **类型:** `number` - **默认值:** `5_000` +- **CLI:** `--testTimeout=5000` 测试的默认超时时间(以毫秒为单位)。设置为 `0` 禁用超时。 diff --git a/website/docs/zh/config/test/unstubEnvs.mdx b/website/docs/zh/config/test/unstubEnvs.mdx index 300bdc44..410d9d94 100644 --- a/website/docs/zh/config/test/unstubEnvs.mdx +++ b/website/docs/zh/config/test/unstubEnvs.mdx @@ -6,5 +6,6 @@ overviewHeaders: [] - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--unstubEnvs` 每次测试时恢复之前使用 [rstest.stubEnv](/api/rstest/utilities#rsteststubenv) 更改的所有 `process.env` 值。 diff --git a/website/docs/zh/config/test/unstubGlobals.mdx b/website/docs/zh/config/test/unstubGlobals.mdx index 6be6f26b..22c2cd4a 100644 --- a/website/docs/zh/config/test/unstubGlobals.mdx +++ b/website/docs/zh/config/test/unstubGlobals.mdx @@ -6,5 +6,6 @@ overviewHeaders: [] - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `--unstubGlobals` 每次测试时恢复之前使用 [rstest.stubGlobal](/api/rstest/utilities#rsteststubglobal) 更改的所有全局变量。 diff --git a/website/docs/zh/config/test/update.mdx b/website/docs/zh/config/test/update.mdx index 7a56e755..66edbdef 100644 --- a/website/docs/zh/config/test/update.mdx +++ b/website/docs/zh/config/test/update.mdx @@ -6,6 +6,7 @@ overviewHeaders: [] - **类型:** `boolean` - **默认值:** `false` +- **CLI:** `-u`, `--update` 是否更新快照。 diff --git a/website/docs/zh/guide/basic/cli.mdx b/website/docs/zh/guide/basic/cli.mdx index 26156dee..de3e18fb 100644 --- a/website/docs/zh/guide/basic/cli.mdx +++ b/website/docs/zh/guide/basic/cli.mdx @@ -92,3 +92,34 @@ $ npx rstest list --json $ npx rstest list --json=./output.json ``` + +## CLI 选项 + +Rstest CLI 支持以下常用参数,所有命令均可使用: + +| 参数 | 说明 | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | 指定配置文件路径(相对或绝对路径),详见 [指定配置文件](/guide/basic/configure-rstest#指定配置文件) | +| `--config-loader ` | 指定配置加载器,可选 `jiti` 或 `native`,详见 [Rsbuild - 指定加载方式](https://rsbuild.rs/guide/configuration/rsbuild#specify-config-loader) | +| `-r, --root ` | 指定项目根目录,详见 [root](/config/test/root) | +| `--globals` | 提供全局 API,详见 [globals](/config/test/globals) | +| `--isolate` | 在隔离环境中运行测试,详见 [isolate](/config/test/isolate) | +| `--exclude ` | 排除指定文件,详见 [exclude](/config/test/exclude) | +| `-u, --update` | 更新快照文件,详见 [update](/config/test/update) | +| `--passWithNoTests` | 当未找到测试文件时允许测试通过,详见 [passWithNoTests](/config/test/passWithNoTests) | +| `--printConsoleTrace` | 调用 console 方法时打印调用栈,详见 [printConsoleTrace](/config/test/printConsoleTrace) | +| `--disableConsoleIntercept` | 禁用 console 拦截,详见 [disableConsoleIntercept](/config/test/disableConsoleIntercept) | +| `--slowTestThreshold ` | 设置测试或套件被视为慢的阈值(毫秒),详见 [slowTestThreshold](/config/test/slowTestThreshold) | +| `-t, --testNamePattern ` | 仅运行名称匹配正则的测试,详见 [testNamePattern](/config/test/testNamePattern) | +| `--testEnvironment ` | 指定测试环境,详见 [testEnvironment](/config/test/testEnvironment) | +| `--testTimeout ` | 设置单个测试的超时时间(毫秒),详见 [testTimeout](/config/test/testTimeout) | +| `--hookTimeout ` | 设置单个测试 hook 的超时时间(毫秒),详见 [hookTimeout](/config/test/hookTimeout) | +| `--retry ` | 测试失败时重试次数,详见 [retry](/config/test/retry) | +| `--maxConcurrency ` | 最大并发测试数,详见 [maxConcurrency](/config/test/maxConcurrency) | +| `--clearMocks` | 每个测试前自动清除 mock 调用、实例、上下文和结果,详见 [clearMocks](/config/test/clearMocks) | +| `--resetMocks` | 每个测试前自动重置 mock 状态,详见 [resetMocks](/config/test/resetMocks) | +| `--restoreMocks` | 每个测试前自动恢复 mock 状态和实现,详见 [restoreMocks](/config/test/restoreMocks) | +| `--unstubGlobals` | 每个测试前恢复被 `rstest.stubGlobal` 修改的全局变量,详见 [unstubGlobals](/config/test/unstubGlobals) | +| `--unstubEnvs` | 每个测试前恢复被 `rstest.stubEnv` 修改的 `process.env`,详见 [unstubEnvs](/config/test/unstubEnvs) | +| `-h, --help` | 显示帮助信息 | +| `-v, --version` | 显示版本号 | pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy