diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae8de13e..286e785a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -108,4 +108,4 @@ jobs: - name: E2E Test if: steps.changes.outputs.changed == 'true' - run: pnpm run e2e + run: pnpm run e2e && pnpm run test:example diff --git a/examples/react/package.json b/examples/react/package.json index 72d3cd2a..939d8ab5 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -4,14 +4,16 @@ "scripts": { "dev": "rsbuild dev --open", "build": "rsbuild build", - "preview": "rsbuild preview" + "preview": "rsbuild preview", + "test": "rstest run", + "test:watch": "rstest" }, "dependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" }, "devDependencies": { - "@rsbuild/core": "^1.4.0-beta.5", + "@rsbuild/core": "1.4.0-rc.0", "@rsbuild/plugin-react": "^1.3.2", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.3.0", diff --git a/package.json b/package.json index 106246df..5bea5f81 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "build": "cross-env NX_DAEMON=false nx run-many -t build --exclude @examples/* --parallel=10", "e2e": "cd tests && pnpm test", "test": "rstest run", + "test:example": "pnpm --filter @examples/* test", "change": "changeset", "changeset": "changeset", "check-dependency-version": "pnpx check-dependency-version-consistency .", @@ -29,12 +30,12 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.4", - "@changesets/cli": "^2.29.4", + "@changesets/cli": "^2.29.5", "@rsdoctor/rspack-plugin": "^1.1.4", "@rstest/core": "workspace:*", "@types/fs-extra": "^11.0.4", "@types/node": "^22.13.8", - "check-dependency-version-consistency": "^5.0.0", + "check-dependency-version-consistency": "^5.0.1", "cross-env": "^7.0.3", "cspell-ban-words": "^0.0.4", "fs-extra": "^11.3.0", @@ -42,7 +43,7 @@ "nano-staged": "^0.8.0", "nx": "^21.2.1", "path-serializer": "0.5.0", - "prettier": "^3.5.3", + "prettier": "^3.6.0", "prettier-plugin-packagejson": "^2.5.15", "simple-git-hooks": "^2.13.0", "typescript": "^5.8.3" diff --git a/packages/core/package.json b/packages/core/package.json index f2b6fedb..2779cebd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@rstest/core", - "version": "0.0.2", + "version": "0.0.3", "description": "The Rsbuild-based test tool.", "bugs": { "url": "https://github.com/web-infra-dev/rstest/issues" @@ -53,7 +53,7 @@ "test": "npx rstest run --globals" }, "dependencies": { - "@rsbuild/core": "^1.4.0-beta.5", + "@rsbuild/core": "1.4.0-rc.0", "@types/chai": "^5.2.2", "@vitest/expect": "^3.2.4", "@vitest/snapshot": "^3.2.4", @@ -79,7 +79,7 @@ "jest-diff": "^30.0.2", "license-webpack-plugin": "^4.0.2", "picocolors": "^1.1.1", - "rslog": "^1.2.7", + "rslog": "^1.2.8", "source-map-support": "^0.5.21", "stacktrace-parser": "0.1.11", "tinyglobby": "^0.2.14", diff --git a/packages/core/rslib.config.ts b/packages/core/rslib.config.ts index 1a24cbb1..ca031714 100644 --- a/packages/core/rslib.config.ts +++ b/packages/core/rslib.config.ts @@ -2,6 +2,8 @@ import { defineConfig } from '@rslib/core'; import { LicenseWebpackPlugin } from 'license-webpack-plugin'; import type { LicenseIdentifiedModule } from 'license-webpack-plugin/dist/LicenseIdentifiedModule'; +const isBuildWatch = process.argv.includes('--watch'); + export default defineConfig({ lib: [ { @@ -54,7 +56,8 @@ export default defineConfig({ ], tools: { rspack: { - plugins: [licensePlugin()], + // fix licensePlugin watch error: ResourceData has been dropped by Rust. + plugins: isBuildWatch ? [] : [licensePlugin()], watchOptions: { ignored: /\.git/, }, diff --git a/packages/core/src/core/plugins/entry.ts b/packages/core/src/core/plugins/entry.ts index 00310867..24027aaf 100644 --- a/packages/core/src/core/plugins/entry.ts +++ b/packages/core/src/core/plugins/entry.ts @@ -42,29 +42,34 @@ export const pluginEntryWatch: (params: { ...setupFiles, }; }; + + config.watchOptions ??= {}; + // TODO: rspack should support `(string | RegExp)[]` type + // https://github.com/web-infra-dev/rspack/issues/10596 + config.watchOptions.ignored = castArray( + config.watchOptions.ignored || [], + ) as string[]; + + if (config.watchOptions.ignored.length === 0) { + config.watchOptions.ignored.push( + // apply default ignored patterns + ...['**/.git', '**/node_modules'], + ); + } + + config.watchOptions.ignored.push(TEMP_RSTEST_OUTPUT_DIR_GLOB); } else { + // watch false seems not effect when rspack.watch() + config.watch = false; + config.watchOptions ??= {}; + config.watchOptions.ignored = '**/**'; + const sourceEntries = await globTestSourceEntries(); config.entry = { ...sourceEntries, ...setupFiles, }; } - - config.watchOptions ??= {}; - // TODO: rspack should support `(string | RegExp)[]` type - // https://github.com/web-infra-dev/rspack/issues/10596 - config.watchOptions.ignored = castArray( - config.watchOptions.ignored || [], - ) as string[]; - - if (config.watchOptions.ignored.length === 0) { - config.watchOptions.ignored.push( - // apply default ignored patterns - ...['**/.git', '**/node_modules'], - ); - } - - config.watchOptions.ignored.push(TEMP_RSTEST_OUTPUT_DIR_GLOB); }); }, }); diff --git a/packages/core/src/core/plugins/ignoreResolveError.ts b/packages/core/src/core/plugins/ignoreResolveError.ts index 280298a8..dd6c5420 100644 --- a/packages/core/src/core/plugins/ignoreResolveError.ts +++ b/packages/core/src/core/plugins/ignoreResolveError.ts @@ -3,12 +3,12 @@ import type { RsbuildPlugin, Rspack } from '@rsbuild/core'; class IgnoreModuleNotFoundErrorPlugin { apply(compiler: Rspack.Compiler) { compiler.hooks.done.tap('Rstest:IgnoreModuleNotFoundPlugin', (stats) => { - stats.compilation.errors = stats.compilation.errors.filter((error) => { - if (/Module not found/.test(error.message)) { - return false; + for (let i = stats.compilation.errors.length - 1; i >= 0; i--) { + if (/Module not found/.test(stats.compilation.errors[i]!.message)) { + // Use `splice` instead of `filter` & `reassign` to avoid communication problems with Rust -> JS -> Rust + stats.compilation.errors.splice(i, 1); } - return true; - }); + } }); } } diff --git a/packages/core/src/core/rsbuild.ts b/packages/core/src/core/rsbuild.ts index c33aa66f..6b2ba473 100644 --- a/packages/core/src/core/rsbuild.ts +++ b/packages/core/src/core/rsbuild.ts @@ -84,10 +84,10 @@ export const prepareRsbuild = async ( testEnvironment, }, } = context; + const debugMode = isDebug(); - RsbuildLogger.level = isDebug() ? 'verbose' : 'error'; - // TODO: find a better way to test outputs - const writeToDisk = process.env.DEBUG_RSTEST_OUTPUTS === 'true'; + RsbuildLogger.level = debugMode ? 'verbose' : 'error'; + const writeToDisk = debugMode; const rsbuildInstance = await createRsbuild({ rsbuildConfig: { @@ -136,8 +136,11 @@ export const prepareRsbuild = async ( config.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]'; config.plugins.push( - new rspack.RstestPlugin({ + new rspack.experiments.RstestPlugin({ injectModulePathName: true, + hoistMockModule: true, + importMetaPathName: true, + manualMockRoot: context.rootPath, }), ); @@ -169,6 +172,10 @@ export const prepareRsbuild = async ( ...(config.optimization || {}), moduleIds: 'named', chunkIds: 'named', + // make sure setup file and test file share the runtime + runtimeChunk: { + name: 'runtime', + }, splitChunks: { chunks: 'all', minSize: 0, @@ -294,7 +301,7 @@ export const createRsbuildServer = async ({ const entries: EntryInfo[] = []; const setupEntries: EntryInfo[] = []; const sourceEntries = await globTestSourceEntries(); - // TODO: check compile error, such as setupFiles not found + for (const entry of Object.keys(entrypoints!)) { const e = entrypoints![entry]!; diff --git a/packages/core/src/runtime/worker/index.ts b/packages/core/src/runtime/worker/index.ts index eb69e167..811537d2 100644 --- a/packages/core/src/runtime/worker/index.ts +++ b/packages/core/src/runtime/worker/index.ts @@ -10,7 +10,7 @@ import './setup'; import { globalApis } from '../../utils/constants'; import { undoSerializableConfig } from '../../utils/helper'; import { formatTestError } from '../util'; -import { loadModule } from './loadModule'; +import { cacheableLoadModule } from './loadModule'; import { createForksRpcOptions, createRuntimeRpc } from './rpc'; import { RstestSnapshotEnvironment } from './snapshot'; @@ -152,7 +152,7 @@ const loadFiles = async ({ for (const { distPath, testPath } of setupEntries) { const setupCodeContent = assetFiles[distPath]!; - await loadModule({ + await cacheableLoadModule({ codeContent: setupCodeContent, distPath, testPath, @@ -162,7 +162,7 @@ const loadFiles = async ({ }); } - await loadModule({ + await cacheableLoadModule({ codeContent: assetFiles[distPath]!, distPath, testPath, diff --git a/packages/core/src/runtime/worker/loadModule.ts b/packages/core/src/runtime/worker/loadModule.ts index ca91aa6b..e780c797 100644 --- a/packages/core/src/runtime/worker/loadModule.ts +++ b/packages/core/src/runtime/worker/loadModule.ts @@ -27,7 +27,7 @@ const createRequire = ( if (content) { try { - return loadModule({ + return cacheableLoadModule({ codeContent: content, testPath: joinedPath, distPath: joinedPath, @@ -50,7 +50,7 @@ const createRequire = ( return require; }; -export const loadModule = ({ +const loadModule = ({ codeContent, distPath, testPath, @@ -135,3 +135,35 @@ export const loadModule = ({ return localModule.exports; }; + +const moduleCache = new Map(); + +export const cacheableLoadModule = ({ + codeContent, + distPath, + testPath, + rstestContext, + assetFiles, + interopDefault, +}: { + interopDefault: boolean; + codeContent: string; + distPath: string; + testPath: string; + rstestContext: Record; + assetFiles: Record; +}): any => { + if (moduleCache.has(testPath)) { + return moduleCache.get(testPath); + } + const mod = loadModule({ + codeContent, + distPath, + testPath, + rstestContext, + assetFiles, + interopDefault, + }); + moduleCache.set(testPath, mod); + return mod; +}; diff --git a/packages/core/src/utils/testFiles.ts b/packages/core/src/utils/testFiles.ts index 53e93200..55406835 100644 --- a/packages/core/src/utils/testFiles.ts +++ b/packages/core/src/utils/testFiles.ts @@ -1,3 +1,4 @@ +import { existsSync } from 'node:fs'; import fs from 'node:fs/promises'; import pathe from 'pathe'; import { glob } from 'tinyglobby'; @@ -39,6 +40,10 @@ export const filterFiles = ( const hasInSourceTestCode = (code: string): boolean => code.includes('import.meta.rstest'); +// format ../setup.ts to _setup~ts +export const formatTestEntryName = (name: string): string => + name.replace(/\.*[/\\]/g, '_').replace(/\./g, '~'); + export const getTestEntries = async ({ include, exclude, @@ -87,8 +92,8 @@ export const getTestEntries = async ({ return Object.fromEntries( filterFiles(testFiles, fileFilters, root).map((entry) => { - const name = pathe.relative(root, entry); - return [name, entry]; + const relativePath = pathe.relative(root, entry); + return [formatTestEntryName(relativePath), entry]; }), ); }; @@ -100,8 +105,16 @@ export const getSetupFiles = ( return Object.fromEntries( castArray(setups).map((setupFile) => { const setupFilePath = getAbsolutePath(rootPath, setupFile); - const name = pathe.relative(rootPath, setupFilePath); - return [name, setupFilePath]; + + if (!existsSync(setupFilePath)) { + let errorMessage = `Setup file ${color.red(setupFile)} not found`; + if (setupFilePath !== setupFile) { + errorMessage += color.gray(` (resolved path: ${setupFilePath})`); + } + throw errorMessage; + } + const relativePath = pathe.relative(rootPath, setupFilePath); + return [formatTestEntryName(relativePath), setupFilePath]; }), ); }; diff --git a/packages/core/tests/core/__snapshots__/rsbuild.test.ts.snap b/packages/core/tests/core/__snapshots__/rsbuild.test.ts.snap index ddfa4779..7f97e49b 100644 --- a/packages/core/tests/core/__snapshots__/rsbuild.test.ts.snap +++ b/packages/core/tests/core/__snapshots__/rsbuild.test.ts.snap @@ -347,6 +347,9 @@ exports[`prepareRsbuild > should generate rspack config correctly 1`] = ` "emitOnErrors": true, "minimize": false, "moduleIds": "named", + "runtimeChunk": { + "name": "runtime", + }, "splitChunks": { "chunks": "all", "maxInitialRequests": Infinity, @@ -414,7 +417,10 @@ exports[`prepareRsbuild > should generate rspack config correctly 1`] = ` RstestPlugin { "_args": [ { + "hoistMockModule": true, + "importMetaPathName": true, "injectModulePathName": true, + "manualMockRoot": undefined, }, ], "affectedHooks": undefined, @@ -435,13 +441,10 @@ exports[`prepareRsbuild > should generate rspack config correctly 1`] = ` ], }, "target": "node", + "watch": false, "watchOptions": { "aggregateTimeout": 0, - "ignored": [ - "**/.git", - "**/node_modules", - "**/dist/.rstest-temp", - ], + "ignored": "**/**", }, } `; diff --git a/packages/core/tests/utils/testFiles.test.ts b/packages/core/tests/utils/testFiles.test.ts index 2b8d41cd..92d728b2 100644 --- a/packages/core/tests/utils/testFiles.test.ts +++ b/packages/core/tests/utils/testFiles.test.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { filterFiles } from '../../src/utils/testFiles'; +import { filterFiles, formatTestEntryName } from '../../src/utils/testFiles'; describe('test filterFiles', () => { it('should filter files correctly', () => { @@ -22,3 +22,10 @@ describe('test filterFiles', () => { expect(filterFiles(testFiles, ['index'], __dirname)).toEqual(testFiles); }); }); + +test('formatTestEntryName', () => { + expect(formatTestEntryName('../setup.ts')).toBe('_setup~ts'); + expect(formatTestEntryName('setup.ts')).toBe('setup~ts'); + expect(formatTestEntryName('some.setup.ts')).toBe('some~setup~ts'); + expect(formatTestEntryName('some/setup.ts')).toBe('some_setup~ts'); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc0b1803..7dcc6ce4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.9.4 version: 1.9.4 '@changesets/cli': - specifier: ^2.29.4 - version: 2.29.4 + specifier: ^2.29.5 + version: 2.29.5 '@rsdoctor/rspack-plugin': specifier: ^1.1.4 version: 1.1.4(@rsbuild/core@1.3.22)(@rspack/core@1.3.12(@swc/helpers@0.5.17)) @@ -27,8 +27,8 @@ importers: specifier: ^22.13.8 version: 22.13.8 check-dependency-version-consistency: - specifier: ^5.0.0 - version: 5.0.0 + specifier: ^5.0.1 + version: 5.0.1 cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -51,11 +51,11 @@ importers: specifier: 0.5.0 version: 0.5.0 prettier: - specifier: ^3.5.3 - version: 3.5.3 + specifier: ^3.6.0 + version: 3.6.0 prettier-plugin-packagejson: specifier: ^2.5.15 - version: 2.5.15(prettier@3.5.3) + version: 2.5.15(prettier@3.6.0) simple-git-hooks: specifier: ^2.13.0 version: 2.13.0 @@ -85,11 +85,11 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@rsbuild/core': - specifier: ^1.4.0-beta.5 - version: 1.4.0-beta.5 + specifier: 1.4.0-rc.0 + version: 1.4.0-rc.0 '@rsbuild/plugin-react': specifier: ^1.3.2 - version: 1.3.2(@rsbuild/core@1.4.0-beta.5) + version: 1.3.2(@rsbuild/core@1.4.0-rc.0) '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 @@ -112,8 +112,8 @@ importers: packages/core: dependencies: '@rsbuild/core': - specifier: ^1.4.0-beta.5 - version: 1.4.0-beta.5 + specifier: 1.4.0-rc.0 + version: 1.4.0-rc.0 '@types/chai': specifier: ^5.2.2 version: 5.2.2 @@ -185,8 +185,8 @@ importers: specifier: ^1.1.1 version: 1.1.1 rslog: - specifier: ^1.2.7 - version: 1.2.7 + specifier: ^1.2.8 + version: 1.2.8 source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -205,8 +205,8 @@ importers: tests: devDependencies: '@rsbuild/core': - specifier: ^1.3.22 - version: 1.3.22 + specifier: 1.4.0-rc.0 + version: 1.4.0-rc.0 '@rslib/core': specifier: 0.10.2 version: 0.10.2(@microsoft/api-extractor@7.52.8(@types/node@22.13.8))(typescript@5.8.3) @@ -280,14 +280,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@rsbuild/core': - specifier: ^1.4.0-beta.5 - version: 1.4.0-beta.5 + specifier: 1.4.0-rc.0 + version: 1.4.0-rc.0 '@rsbuild/plugin-react': specifier: ^1.3.2 - version: 1.3.2(@rsbuild/core@1.4.0-beta.5) + version: 1.3.2(@rsbuild/core@1.4.0-rc.0) '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 '@testing-library/react': specifier: ^16.3.0 version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -339,7 +342,7 @@ importers: devDependencies: '@rsbuild/plugin-react': specifier: ^1.3.2 - version: 1.3.2(@rsbuild/core@1.4.0-beta.5) + version: 1.3.2(@rsbuild/core@1.4.0-rc.0) '@types/react': specifier: ^19.1.8 version: 19.1.8 @@ -360,7 +363,7 @@ importers: devDependencies: '@rsbuild/plugin-sass': specifier: ^1.3.2 - version: 1.3.2(@rsbuild/core@1.4.0-beta.5) + version: 1.3.2(@rsbuild/core@1.4.0-rc.0) '@rspress/plugin-llms': specifier: 2.0.0-beta.16 version: 2.0.0-beta.16(@rspress/core@2.0.0-beta.16(@types/react@19.1.8)(acorn@8.14.1)) @@ -387,10 +390,10 @@ importers: version: 19.1.0(react@19.1.0) rsbuild-plugin-google-analytics: specifier: ^1.0.3 - version: 1.0.3(@rsbuild/core@1.4.0-beta.5) + version: 1.0.3(@rsbuild/core@1.4.0-rc.0) rsbuild-plugin-open-graph: specifier: ^1.0.2 - version: 1.0.2(@rsbuild/core@1.4.0-beta.5) + version: 1.0.2(@rsbuild/core@1.4.0-rc.0) rspress: specifier: ^2.0.0-beta.16 version: 2.0.0-beta.16(@types/react@19.1.8)(acorn@8.14.1) @@ -406,6 +409,9 @@ importers: packages: + '@adobe/css-tools@4.4.3': + resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -542,14 +548,14 @@ packages: '@changesets/apply-release-plan@7.0.12': resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} - '@changesets/assemble-release-plan@6.0.8': - resolution: {integrity: sha512-y8+8LvZCkKJdbUlpXFuqcavpzJR80PN0OIfn8HZdwK7Sh6MgLXm4hKY5vu6/NDoKp8lAlM4ERZCqRMLxP4m+MQ==} + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} '@changesets/changelog-git@0.2.1': resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@changesets/cli@2.29.4': - resolution: {integrity: sha512-VW30x9oiFp/un/80+5jLeWgEU6Btj8IqOgI+X/zAYu4usVOWXjPIK5jSSlt5jsCU7/6Z7AxEkarxBxGUqkAmNg==} + '@changesets/cli@2.29.5': + resolution: {integrity: sha512-0j0cPq3fgxt2dPdFsg4XvO+6L66RC0pZybT9F4dG5TBrLA3jA/1pNkdTXH9IBBVHkgsKrNKenI3n1mPyPlIydg==} hasBin: true '@changesets/config@3.1.1': @@ -561,8 +567,8 @@ packages: '@changesets/get-dependents-graph@2.1.3': resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} - '@changesets/get-release-plan@4.0.12': - resolution: {integrity: sha512-KukdEgaafnyGryUwpHG2kZ7xJquOmWWWk5mmoeQaSvZTWH1DC5D/Sw6ClgGFYtQnOMSQhgoEbDxAbpIIayKH1g==} + '@changesets/get-release-plan@4.0.13': + resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} @@ -827,8 +833,8 @@ packages: engines: {node: '>=16.10.0'} hasBin: true - '@rsbuild/core@1.4.0-beta.5': - resolution: {integrity: sha512-p9/ShPxTo0Jc/umZOHKOefBLydCKXnBxck50y1bLjwpiiK13+vN09l8L/GM9G7sALhVIzWxcMFBt39IdV1ePFA==} + '@rsbuild/core@1.4.0-rc.0': + resolution: {integrity: sha512-GgiHay9R5AdYXgboMwhKoaCZibfy39pZ+jlnAA9YQiorIgvPDZe58U/RWMx4q7amWzgTyge/wvMkNM1ifZR/3w==} engines: {node: '>=16.10.0'} hasBin: true @@ -907,6 +913,11 @@ packages: cpu: [arm64] os: [darwin] + '@rspack/binding-darwin-arm64@1.4.0-rc.0': + resolution: {integrity: sha512-4fJ577AVWSHHsY+FEozxhYpnSGZmIneusFhIpbkf7l3x8hh5SdL6hE2y3lNeE9BHgjHPfdMJogoz/VNYcTWG/A==} + cpu: [arm64] + os: [darwin] + '@rspack/binding-darwin-x64@1.3.12': resolution: {integrity: sha512-Sj4m+mCUxL7oCpdu7OmWT7fpBM7hywk5CM9RDc3D7StaBZbvNtNftafCrTZzTYKuZrKmemTh5SFzT5Tz7tf6GA==} cpu: [x64] @@ -917,6 +928,11 @@ packages: cpu: [x64] os: [darwin] + '@rspack/binding-darwin-x64@1.4.0-rc.0': + resolution: {integrity: sha512-6r4l/2VhPuHrQrDYazQl4GNTSPvPPXEZwee2fYVO6YeWiPPJFNAUyIT0DjXtAMuJ2zDBOH0DkJ6dqkLf9/kn8Q==} + cpu: [x64] + os: [darwin] + '@rspack/binding-linux-arm64-gnu@1.3.12': resolution: {integrity: sha512-7MuOxf3/Mhv4mgFdLTvgnt/J+VouNR65DEhorth+RZm3LEWojgoFEphSAMAvpvAOpYSS68Sw4SqsOZi719ia2w==} cpu: [arm64] @@ -927,6 +943,11 @@ packages: cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-gnu@1.4.0-rc.0': + resolution: {integrity: sha512-K6VLea9StRkOltXqyxNzKeNR3cAFsOpHoNDc8lg0zZNAr1Rn1f+8THqFlWfDV1djXvhM/LsBM+rG97yQFYh/zg==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-arm64-musl@1.3.12': resolution: {integrity: sha512-s6KKj20T9Z1bA8caIjU6EzJbwyDo1URNFgBAlafCT2UC6yX7flstDJJ38CxZacA9A2P24RuQK2/jPSZpWrTUFA==} cpu: [arm64] @@ -937,6 +958,11 @@ packages: cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-musl@1.4.0-rc.0': + resolution: {integrity: sha512-/xAgd0ObvI9ggJWMHDI8WHFCeuqi7gMnzdeN8QWO82dzlwO/0rgBqQkULS1hUDlV47Hh03BwjUz9sbuXCnelqg==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-x64-gnu@1.3.12': resolution: {integrity: sha512-0w/sRREYbRgHgWvs2uMEJSLfvzbZkPHUg6CMcYQGNVK6axYRot6jPyKetyFYA9pR5fB5rsXegpnFaZaVrRIK2g==} cpu: [x64] @@ -947,6 +973,11 @@ packages: cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-gnu@1.4.0-rc.0': + resolution: {integrity: sha512-2u6ytaatpksqUfEn16M/8ppkYaurQpPgZ7cCy3iFpVi6w+7loXWz0X+xklsuBH8H0ie4CZkLmiJIhkW2hFn8KA==} + cpu: [x64] + os: [linux] + '@rspack/binding-linux-x64-musl@1.3.12': resolution: {integrity: sha512-jEdxkPymkRxbijDRsBGdhopcbGXiXDg59lXqIRkVklqbDmZ/O6DHm7gImmlx5q9FoWbz0gqJuOKBz4JqWxjWVA==} cpu: [x64] @@ -957,10 +988,19 @@ packages: cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-musl@1.4.0-rc.0': + resolution: {integrity: sha512-ilSb6GDz/0A+qlnPFZJuw9DtFH/ENf09f7raXxye3faZw/GH8aJ9H3X8VNeMR1QrYwFFk8LLB402EtZHETyz7Q==} + cpu: [x64] + os: [linux] + '@rspack/binding-wasm32-wasi@1.4.0-beta.1': resolution: {integrity: sha512-B1+gtkjvXnXcoUU5+ETO3NEiH4Zub3bFJu38sSNp4blsG9cRSbHtyNTpZ3M81LttltMJpcwlprXvfu42RSbfSA==} cpu: [wasm32] + '@rspack/binding-wasm32-wasi@1.4.0-rc.0': + resolution: {integrity: sha512-NsnAfBrQDlZTgudxG2YNgnOsZgaE4Vqm1pM0OXWd6NOhSGwGQ6T/rka99dHiUTxxAb6AOqI/avVn1NYsPHsqIQ==} + cpu: [wasm32] + '@rspack/binding-win32-arm64-msvc@1.3.12': resolution: {integrity: sha512-ZRvUCb3TDLClAqcTsl/o9UdJf0B5CgzAxgdbnYJbldyuyMeTUB4jp20OfG55M3C2Nute2SNhu2bOOp9Se5Ongw==} cpu: [arm64] @@ -971,6 +1011,11 @@ packages: cpu: [arm64] os: [win32] + '@rspack/binding-win32-arm64-msvc@1.4.0-rc.0': + resolution: {integrity: sha512-dT7FZz0dbWKzb3Ka6OD0TkOhGS/qC2/tWJ98nIxXMFCW2ZcULJhwcnRe95KBEwVDzwHgcTTzVa3fUFuTmcL87w==} + cpu: [arm64] + os: [win32] + '@rspack/binding-win32-ia32-msvc@1.3.12': resolution: {integrity: sha512-1TKPjuXStPJr14f3ZHuv40Xc/87jUXx10pzVtrPnw+f3hckECHrbYU/fvbVzZyuXbsXtkXpYca6ygCDRJAoNeQ==} cpu: [ia32] @@ -981,6 +1026,11 @@ packages: cpu: [ia32] os: [win32] + '@rspack/binding-win32-ia32-msvc@1.4.0-rc.0': + resolution: {integrity: sha512-0Q8+vWvT6zZPkNqaUvBIfXUSC3ZHsu/2on1bX9bGWLVfG4Or1QWY9vNA3vPX8DsK3XiHwixX+iLo95tmQgasNw==} + cpu: [ia32] + os: [win32] + '@rspack/binding-win32-x64-msvc@1.3.12': resolution: {integrity: sha512-lCR0JfnYKpV+a6r2A2FdxyUKUS4tajePgpPJN5uXDgMGwrDtRqvx+d0BHhwjFudQVJq9VVbRaL89s2MQ6u+xYw==} cpu: [x64] @@ -991,12 +1041,20 @@ packages: cpu: [x64] os: [win32] + '@rspack/binding-win32-x64-msvc@1.4.0-rc.0': + resolution: {integrity: sha512-bsKJGM6Tu6aqyt6QTDEPL0BCtJ/HWHF3phdCP9XBUcmlSvjIwemekTs/QO/k2ZKXQ93j9Sz8J92WWmNQp0Mp8w==} + cpu: [x64] + os: [win32] + '@rspack/binding@1.3.12': resolution: {integrity: sha512-4Ic8lV0+LCBfTlH5aIOujIRWZOtgmG223zC4L3o8WY/+ESAgpdnK6lSSMfcYgRanYLAy3HOmFIp20jwskMpbAg==} '@rspack/binding@1.4.0-beta.1': resolution: {integrity: sha512-cHtpiH0Iv7MrTrQCTPGwm0ourL6X82BCSK4tfmkwEOodMfCVkezG16bC0MCRKdaJCG/dehj594TnghwGldzj1A==} + '@rspack/binding@1.4.0-rc.0': + resolution: {integrity: sha512-kBEzks6RymjBLYF84TkUP895yCqqlodHDmBsWKbJGOokNKx+0ohnoWxXws5oZca/j9DSKCdEsA8VyROtqdMujw==} + '@rspack/core@1.3.12': resolution: {integrity: sha512-mAPmV4LPPRgxpouUrGmAE4kpF1NEWJGyM5coebsjK/zaCMSjw3mkdxiU2b5cO44oIi0Ifv5iGkvwbdrZOvMyFA==} engines: {node: '>=16.0.0'} @@ -1015,6 +1073,15 @@ packages: '@swc/helpers': optional: true + '@rspack/core@1.4.0-rc.0': + resolution: {integrity: sha512-yO4AP7sgptepks2kNLFvLipdonGv6OKDUIKEl0c7SpbBmPEspd3vsYxE/T5hruFVDnq0GPEePKA1GOjRCKGR8A==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + '@rspack/lite-tapable@1.0.1': resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} engines: {node: '>=16.0.0'} @@ -1197,6 +1264,10 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/react@16.3.0': resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} engines: {node: '>=18'} @@ -1534,6 +1605,10 @@ packages: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1557,8 +1632,8 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - check-dependency-version-consistency@5.0.0: - resolution: {integrity: sha512-7UNH6QDgPHImF5TmmYH0mSpbnU5KhUT8Z+27hpW/fJM8VWnZpz6INh/olHmVPqFOUm3+lES4R8LwBLHrh9XJqg==} + check-dependency-version-consistency@5.0.1: + resolution: {integrity: sha512-Hpf7lgElsLVCTJKjFHEVH76Jbf1DnnJ7IJK+zN2b+pRIF0Cs5Girjw1lzWgyV/3QGUR3D6goL0oN6mQ6DlrDCw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -1610,8 +1685,8 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} commander@7.2.0: @@ -1658,6 +1733,9 @@ packages: cspell-ban-words@0.0.4: resolution: {integrity: sha512-w+18WPFAEmo2F+Fr4L29+GdY5ckOLN95WPwb/arfrtuzzB5VzQRFyIujo0T7pq+xFE0Z2gjfLn33Wk/u5ctNQQ==} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssstyle@4.4.0: resolution: {integrity: sha512-W0Y2HOXlPkb2yaKrCVRjinYKciu/qSLEmK0K9mcfDei3zwlnHFEHAs/Du3cIRwPqY+J4JsiBzUjoHyc8RsJ03A==} engines: {node: '>=18'} @@ -1771,6 +1849,9 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -2230,6 +2311,10 @@ packages: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2677,6 +2762,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@3.0.8: resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} @@ -2920,8 +3009,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + prettier@3.6.0: + resolution: {integrity: sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==} engines: {node: '>=14'} hasBin: true @@ -3026,6 +3115,10 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + reduce-configs@1.1.0: resolution: {integrity: sha512-DQxy6liNadHfrLahZR7lMdc227NYVaQZhY5FMsxLEjX8X0SCuH+ESHSLCoz2yDZFq1/CLMDOAHdsEHwOEXKtvg==} @@ -3129,8 +3222,8 @@ packages: '@rsbuild/core': optional: true - rslog@1.2.7: - resolution: {integrity: sha512-8fnO9sQfJ4wxg1rCoden42V9A1TloS8HfgvSXZg8lZjgP74iM+PnlV8Sj4+9ouRP8juWx5qkO/+GFjTKAf2s0Q==} + rslog@1.2.8: + resolution: {integrity: sha512-BXUB5LnElxG0n9dSS+1Num4q+U+GGuCasi2/8I6hYMyZm2+L5kUGvv7pAc6z7+ODxFXVV6AHy9mSa2VSoauk+g==} rspack-plugin-virtual-module@1.0.1: resolution: {integrity: sha512-NQJ3fXa1v0WayvfHMWbyqLUA3JIqgCkhIcIOnZscuisinxorQyIAo+bqcU5pCusMKSyPqVIWO3caQyl0s9VDAg==} @@ -3464,6 +3557,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -3820,6 +3917,8 @@ packages: snapshots: + '@adobe/css-tools@4.4.3': {} + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -3938,7 +4037,7 @@ snapshots: resolve-from: 5.0.0 semver: 7.7.2 - '@changesets/assemble-release-plan@6.0.8': + '@changesets/assemble-release-plan@6.0.9': dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.3 @@ -3951,15 +4050,15 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.29.4': + '@changesets/cli@2.29.5': dependencies: '@changesets/apply-release-plan': 7.0.12 - '@changesets/assemble-release-plan': 6.0.8 + '@changesets/assemble-release-plan': 6.0.9 '@changesets/changelog-git': 0.2.1 '@changesets/config': 3.1.1 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.3 - '@changesets/get-release-plan': 4.0.12 + '@changesets/get-release-plan': 4.0.13 '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 '@changesets/pre': 2.0.2 @@ -4003,9 +4102,9 @@ snapshots: picocolors: 1.1.1 semver: 7.7.2 - '@changesets/get-release-plan@4.0.12': + '@changesets/get-release-plan@4.0.13': dependencies: - '@changesets/assemble-release-plan': 6.0.8 + '@changesets/assemble-release-plan': 6.0.9 '@changesets/config': 3.1.1 '@changesets/pre': 2.0.2 '@changesets/read': 0.6.5 @@ -4348,9 +4447,9 @@ snapshots: core-js: 3.43.0 jiti: 2.4.2 - '@rsbuild/core@1.4.0-beta.5': + '@rsbuild/core@1.4.0-rc.0': dependencies: - '@rspack/core': 1.4.0-beta.1(@swc/helpers@0.5.17) + '@rspack/core': 1.4.0-rc.0(@swc/helpers@0.5.17) '@rspack/lite-tapable': 1.0.1 '@swc/helpers': 0.5.17 core-js: 3.43.0 @@ -4374,17 +4473,17 @@ snapshots: transitivePeerDependencies: - webpack-hot-middleware - '@rsbuild/plugin-react@1.3.2(@rsbuild/core@1.4.0-beta.5)': + '@rsbuild/plugin-react@1.3.2(@rsbuild/core@1.4.0-rc.0)': dependencies: - '@rsbuild/core': 1.4.0-beta.5 + '@rsbuild/core': 1.4.0-rc.0 '@rspack/plugin-react-refresh': 1.4.3(react-refresh@0.17.0) react-refresh: 0.17.0 transitivePeerDependencies: - webpack-hot-middleware - '@rsbuild/plugin-sass@1.3.2(@rsbuild/core@1.4.0-beta.5)': + '@rsbuild/plugin-sass@1.3.2(@rsbuild/core@1.4.0-rc.0)': dependencies: - '@rsbuild/core': 1.4.0-beta.5 + '@rsbuild/core': 1.4.0-rc.0 deepmerge: 4.3.1 loader-utils: 2.0.4 postcss: 8.5.4 @@ -4502,7 +4601,7 @@ snapshots: json-stream-stringify: 3.0.1 lines-and-columns: 2.0.4 picocolors: 1.1.1 - rslog: 1.2.7 + rslog: 1.2.8 strip-ansi: 6.0.1 transitivePeerDependencies: - '@rspack/core' @@ -4524,59 +4623,91 @@ snapshots: '@rspack/binding-darwin-arm64@1.4.0-beta.1': optional: true + '@rspack/binding-darwin-arm64@1.4.0-rc.0': + optional: true + '@rspack/binding-darwin-x64@1.3.12': optional: true '@rspack/binding-darwin-x64@1.4.0-beta.1': optional: true + '@rspack/binding-darwin-x64@1.4.0-rc.0': + optional: true + '@rspack/binding-linux-arm64-gnu@1.3.12': optional: true '@rspack/binding-linux-arm64-gnu@1.4.0-beta.1': optional: true + '@rspack/binding-linux-arm64-gnu@1.4.0-rc.0': + optional: true + '@rspack/binding-linux-arm64-musl@1.3.12': optional: true '@rspack/binding-linux-arm64-musl@1.4.0-beta.1': optional: true + '@rspack/binding-linux-arm64-musl@1.4.0-rc.0': + optional: true + '@rspack/binding-linux-x64-gnu@1.3.12': optional: true '@rspack/binding-linux-x64-gnu@1.4.0-beta.1': optional: true + '@rspack/binding-linux-x64-gnu@1.4.0-rc.0': + optional: true + '@rspack/binding-linux-x64-musl@1.3.12': optional: true '@rspack/binding-linux-x64-musl@1.4.0-beta.1': optional: true + '@rspack/binding-linux-x64-musl@1.4.0-rc.0': + optional: true + '@rspack/binding-wasm32-wasi@1.4.0-beta.1': dependencies: '@napi-rs/wasm-runtime': 0.2.11 optional: true + '@rspack/binding-wasm32-wasi@1.4.0-rc.0': + dependencies: + '@napi-rs/wasm-runtime': 0.2.11 + optional: true + '@rspack/binding-win32-arm64-msvc@1.3.12': optional: true '@rspack/binding-win32-arm64-msvc@1.4.0-beta.1': optional: true + '@rspack/binding-win32-arm64-msvc@1.4.0-rc.0': + optional: true + '@rspack/binding-win32-ia32-msvc@1.3.12': optional: true '@rspack/binding-win32-ia32-msvc@1.4.0-beta.1': optional: true + '@rspack/binding-win32-ia32-msvc@1.4.0-rc.0': + optional: true + '@rspack/binding-win32-x64-msvc@1.3.12': optional: true '@rspack/binding-win32-x64-msvc@1.4.0-beta.1': optional: true + '@rspack/binding-win32-x64-msvc@1.4.0-rc.0': + optional: true + '@rspack/binding@1.3.12': optionalDependencies: '@rspack/binding-darwin-arm64': 1.3.12 @@ -4602,6 +4733,19 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.4.0-beta.1 '@rspack/binding-win32-x64-msvc': 1.4.0-beta.1 + '@rspack/binding@1.4.0-rc.0': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.4.0-rc.0 + '@rspack/binding-darwin-x64': 1.4.0-rc.0 + '@rspack/binding-linux-arm64-gnu': 1.4.0-rc.0 + '@rspack/binding-linux-arm64-musl': 1.4.0-rc.0 + '@rspack/binding-linux-x64-gnu': 1.4.0-rc.0 + '@rspack/binding-linux-x64-musl': 1.4.0-rc.0 + '@rspack/binding-wasm32-wasi': 1.4.0-rc.0 + '@rspack/binding-win32-arm64-msvc': 1.4.0-rc.0 + '@rspack/binding-win32-ia32-msvc': 1.4.0-rc.0 + '@rspack/binding-win32-x64-msvc': 1.4.0-rc.0 + '@rspack/core@1.3.12(@swc/helpers@0.5.17)': dependencies: '@module-federation/runtime-tools': 0.14.0 @@ -4619,6 +4763,14 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.17 + '@rspack/core@1.4.0-rc.0(@swc/helpers@0.5.17)': + dependencies: + '@module-federation/runtime-tools': 0.15.0 + '@rspack/binding': 1.4.0-rc.0 + '@rspack/lite-tapable': 1.0.1 + optionalDependencies: + '@swc/helpers': 0.5.17 + '@rspack/lite-tapable@1.0.1': {} '@rspack/plugin-react-refresh@1.4.3(react-refresh@0.17.0)': @@ -4894,6 +5046,16 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@testing-library/jest-dom@6.6.3': + dependencies: + '@adobe/css-tools': 4.4.3 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.26.9 @@ -5246,6 +5408,11 @@ snapshots: loupe: 3.1.4 pathval: 2.0.0 + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -5263,11 +5430,11 @@ snapshots: chardet@0.7.0: {} - check-dependency-version-consistency@5.0.0: + check-dependency-version-consistency@5.0.1: dependencies: '@types/js-yaml': 4.0.9 chalk: 5.4.1 - commander: 12.1.0 + commander: 13.1.0 edit-json-file: 1.8.1 globby: 14.1.0 js-yaml: 4.1.0 @@ -5321,7 +5488,7 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@12.1.0: {} + commander@13.1.0: {} commander@7.2.0: {} @@ -5365,6 +5532,8 @@ snapshots: cspell-ban-words@0.0.4: {} + css.escape@1.5.1: {} + cssstyle@4.4.0: dependencies: '@asamuzakjp/css-color': 3.2.0 @@ -5444,6 +5613,8 @@ snapshots: dom-accessibility-api@0.5.16: {} + dom-accessibility-api@0.6.3: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -6019,6 +6190,8 @@ snapshots: import-lazy@4.0.0: {} + indent-string@4.0.0: {} + inherits@2.0.4: {} inline-style-parser@0.2.4: {} @@ -6691,6 +6864,8 @@ snapshots: mimic-fn@2.1.0: {} + min-indent@1.0.1: {} + minimatch@3.0.8: dependencies: brace-expansion: 1.1.11 @@ -6929,16 +7104,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prettier-plugin-packagejson@2.5.15(prettier@3.5.3): + prettier-plugin-packagejson@2.5.15(prettier@3.6.0): dependencies: sort-package-json: 3.2.1 synckit: 0.11.8 optionalDependencies: - prettier: 3.5.3 + prettier: 3.6.0 prettier@2.8.8: {} - prettier@3.5.3: {} + prettier@3.6.0: {} pretty-format@27.5.1: dependencies: @@ -7059,6 +7234,11 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + reduce-configs@1.1.0: {} regenerator-runtime@0.14.1: {} @@ -7181,15 +7361,15 @@ snapshots: '@microsoft/api-extractor': 7.52.8(@types/node@22.13.8) typescript: 5.8.3 - rsbuild-plugin-google-analytics@1.0.3(@rsbuild/core@1.4.0-beta.5): + rsbuild-plugin-google-analytics@1.0.3(@rsbuild/core@1.4.0-rc.0): optionalDependencies: - '@rsbuild/core': 1.4.0-beta.5 + '@rsbuild/core': 1.4.0-rc.0 - rsbuild-plugin-open-graph@1.0.2(@rsbuild/core@1.4.0-beta.5): + rsbuild-plugin-open-graph@1.0.2(@rsbuild/core@1.4.0-rc.0): optionalDependencies: - '@rsbuild/core': 1.4.0-beta.5 + '@rsbuild/core': 1.4.0-rc.0 - rslog@1.2.7: {} + rslog@1.2.8: {} rspack-plugin-virtual-module@1.0.1: dependencies: @@ -7520,6 +7700,10 @@ snapshots: strip-bom@3.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} style-to-js@1.1.16: diff --git a/scripts/dictionary.txt b/scripts/dictionary.txt index 933aad2c..62768905 100644 --- a/scripts/dictionary.txt +++ b/scripts/dictionary.txt @@ -54,6 +54,7 @@ jfif jiti jridgewell jscpuprofile +jsdom jsesc jsxs koppers diff --git a/tests/jsdom/fixtures/package.json b/tests/jsdom/fixtures/package.json index 614a7f31..79434378 100644 --- a/tests/jsdom/fixtures/package.json +++ b/tests/jsdom/fixtures/package.json @@ -11,8 +11,9 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@rsbuild/core": "^1.4.0-beta.5", + "@rsbuild/core": "1.4.0-rc.0", "@rsbuild/plugin-react": "^1.3.2", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.3.0", "@types/react": "^19.1.8", diff --git a/tests/jsdom/fixtures/rstest.config.ts b/tests/jsdom/fixtures/rstest.config.ts index 935dc49c..7addc948 100644 --- a/tests/jsdom/fixtures/rstest.config.ts +++ b/tests/jsdom/fixtures/rstest.config.ts @@ -4,4 +4,5 @@ import rsbuildConfig from './rsbuild.config'; export default defineConfig({ ...(rsbuildConfig as RstestConfig), testEnvironment: 'jsdom', + setupFiles: ['./test/setup.ts'], }); diff --git a/tests/jsdom/fixtures/test/jestDom.test.tsx b/tests/jsdom/fixtures/test/jestDom.test.tsx new file mode 100644 index 00000000..13a16e22 --- /dev/null +++ b/tests/jsdom/fixtures/test/jestDom.test.tsx @@ -0,0 +1,12 @@ +import { expect, test } from '@rstest/core'; +import { screen } from '@testing-library/dom'; + +test('uses jest-dom', () => { + document.body.innerHTML = ` + +
Visible Example
+ `; + + expect(screen.queryByTestId('not-empty')).not.toBeEmptyDOMElement(); + expect(screen.getByText('Visible Example')).toBeVisible(); +}); diff --git a/tests/jsdom/fixtures/test/setup.ts b/tests/jsdom/fixtures/test/setup.ts new file mode 100644 index 00000000..32b38ae2 --- /dev/null +++ b/tests/jsdom/fixtures/test/setup.ts @@ -0,0 +1,4 @@ +import { expect } from '@rstest/core'; +import * as jestDomMatchers from '@testing-library/jest-dom/matchers'; + +expect.extend(jestDomMatchers); diff --git a/tests/jsdom/fixtures/test/test.d.ts b/tests/jsdom/fixtures/test/test.d.ts new file mode 100755 index 00000000..e48dee96 --- /dev/null +++ b/tests/jsdom/fixtures/test/test.d.ts @@ -0,0 +1 @@ +/// diff --git a/tests/jsdom/index.test.ts b/tests/jsdom/index.test.ts index d9ad17f0..6b0b3488 100644 --- a/tests/jsdom/index.test.ts +++ b/tests/jsdom/index.test.ts @@ -19,3 +19,17 @@ it('should run jsdom test correctly', async () => { 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/package.json b/tests/package.json index 07139ae6..2f3c1294 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,7 +6,7 @@ "test": "rstest run" }, "devDependencies": { - "@rsbuild/core": "^1.3.22", + "@rsbuild/core": "1.4.0-rc.0", "@rslib/core": "0.10.2", "@rstest/core": "workspace:*", "jest-image-snapshot": "^6.5.1", diff --git a/tests/singleton/fixtures/a.ts b/tests/singleton/fixtures/a.ts new file mode 100644 index 00000000..2940beec --- /dev/null +++ b/tests/singleton/fixtures/a.ts @@ -0,0 +1,8 @@ +let a: string; + +export const getA = () => { + if (!a) { + a = Math.ceil(Math.random() * 1000).toString(); + } + return a; +}; diff --git a/tests/singleton/fixtures/b.ts b/tests/singleton/fixtures/b.ts new file mode 100644 index 00000000..dc2426eb --- /dev/null +++ b/tests/singleton/fixtures/b.ts @@ -0,0 +1,8 @@ +let b: string; + +export const getB = () => { + if (!b) { + b = Math.ceil(Math.random() * 1000).toString(); + } + return b; +}; diff --git a/tests/singleton/fixtures/index.test.ts b/tests/singleton/fixtures/index.test.ts new file mode 100644 index 00000000..c99a3084 --- /dev/null +++ b/tests/singleton/fixtures/index.test.ts @@ -0,0 +1,11 @@ +import { expect, it } from '@rstest/core'; +import { getA } from './a'; + +it('should singleton A', () => { + expect(getA()).toBe(process.env.A); +}); + +it('should singleton B', async () => { + const { getB } = await import('./b'); + expect(getB()).toBe(process.env.B); +}); diff --git a/tests/singleton/fixtures/rstest.config.ts b/tests/singleton/fixtures/rstest.config.ts new file mode 100644 index 00000000..4289fdbb --- /dev/null +++ b/tests/singleton/fixtures/rstest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + setupFiles: ['./setup.ts'], +}); diff --git a/tests/singleton/fixtures/setup.ts b/tests/singleton/fixtures/setup.ts new file mode 100644 index 00000000..26aa1042 --- /dev/null +++ b/tests/singleton/fixtures/setup.ts @@ -0,0 +1,12 @@ +import { beforeAll } from '@rstest/core'; +import { getA } from './a'; + +beforeAll(async () => { + const A = getA(); + + process.env.A = A; + const { getB } = await import('./b'); + + const B = getB(); + process.env.B = B; +}); diff --git a/tests/singleton/index.test.ts b/tests/singleton/index.test.ts new file mode 100644 index 00000000..54f205af --- /dev/null +++ b/tests/singleton/index.test.ts @@ -0,0 +1,24 @@ +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, it } from '@rstest/core'; +import { runRstestCli } from '../scripts/'; + +const __filename = fileURLToPath(import.meta.url); + +const __dirname = dirname(__filename); + +describe('test singleton', () => { + it('should load singleton module correctly', async () => { + const { expectExecSuccess } = await runRstestCli({ + command: 'rstest', + args: ['run', 'index.test.ts'], + options: { + nodeOptions: { + cwd: join(__dirname, 'fixtures'), + }, + }, + }); + + await expectExecSuccess(); + }); +}); diff --git a/tests/test-api/edgeCase.test.ts b/tests/test-api/edgeCase.test.ts index 5591e894..b3743d9c 100644 --- a/tests/test-api/edgeCase.test.ts +++ b/tests/test-api/edgeCase.test.ts @@ -44,6 +44,29 @@ describe('Test Edge Cases', () => { expect(logs.find((log) => log.includes('Tests 2 passed'))).toBeTruthy(); }); + it('should log build error message correctly', async () => { + const { cli } = await runRstestCli({ + command: 'rstest', + args: ['run', 'fixtures/lessError.test.ts'], + options: { + nodeOptions: { + cwd: __dirname, + }, + }, + }); + + await cli.exec; + expect(cli.exec.process?.exitCode).toBe(1); + + const logs = cli.stdout.split('\n').filter(Boolean); + + // no `× [object Object]` + expect(logs.find((log) => log.includes('[object Object]'))).toBeFalsy(); + expect( + logs.find((log) => log.includes('To enable support for Less')), + ).toBeTruthy(); + }); + it('only in skip suite', async () => { const { cli, expectExecSuccess } = await runRstestCli({ command: 'rstest', diff --git a/tests/test-api/fixtures/index.module.less b/tests/test-api/fixtures/index.module.less new file mode 100644 index 00000000..fc948b17 --- /dev/null +++ b/tests/test-api/fixtures/index.module.less @@ -0,0 +1,3 @@ +.page { + display: flex; +} diff --git a/tests/test-api/fixtures/lessError.test.ts b/tests/test-api/fixtures/lessError.test.ts new file mode 100644 index 00000000..79c96bd0 --- /dev/null +++ b/tests/test-api/fixtures/lessError.test.ts @@ -0,0 +1,7 @@ +import { expect, it } from '@rstest/core'; +// @ts-expect-error +import style from './index.module.less'; + +it('test', () => { + expect(style).toBeDefined(); +}); diff --git a/website/docs/en/_meta.json b/website/docs/en/_meta.json index 98867201..b74f70b1 100644 --- a/website/docs/en/_meta.json +++ b/website/docs/en/_meta.json @@ -8,5 +8,10 @@ "text": "Config", "link": "/config/", "activeMatch": "/config/" + }, + { + "text": "API", + "link": "/api/", + "activeMatch": "/api/" } ] diff --git a/website/docs/en/api/_meta.json b/website/docs/en/api/_meta.json new file mode 100644 index 00000000..03890893 --- /dev/null +++ b/website/docs/en/api/_meta.json @@ -0,0 +1,12 @@ +[ + { + "type": "file", + "name": "index", + "label": "Overview" + }, + { + "type": "dir", + "name": "rstest", + "label": "Rstest Utility" + } +] diff --git a/website/docs/en/api/index.mdx b/website/docs/en/api/index.mdx new file mode 100644 index 00000000..dd495b97 --- /dev/null +++ b/website/docs/en/api/index.mdx @@ -0,0 +1,6 @@ +--- +overview: true +title: API Overview +--- + +This page lists all the testing APIs for Rstest. diff --git a/website/docs/en/api/rstest/index.mdx b/website/docs/en/api/rstest/index.mdx new file mode 100644 index 00000000..10b28d9f --- /dev/null +++ b/website/docs/en/api/rstest/index.mdx @@ -0,0 +1,25 @@ +--- +title: Rstest Utility +--- + +# Rstest utility + +Rstest provides utility functions to help you out through its `rstest` helper. + +You can import it from `@rstest/core` directly, and you can also use its alias `rs`. + +```ts +import { rstest } from '@rstest/core'; + +const fn = rstest.fn(); +fn.mockResolvedValue('foo'); +``` + +```ts +import { rs } from '@rstest/core'; + +const fn = rs.fn(); +fn.mockResolvedValue('foo'); +``` + +Or, you can access it globally like `jest` (when [globals](/config/test/globals) configuration is enabled). diff --git a/website/docs/en/api/rstest/mockFunctions.mdx b/website/docs/en/api/rstest/mockFunctions.mdx new file mode 100644 index 00000000..e8af4915 --- /dev/null +++ b/website/docs/en/api/rstest/mockFunctions.mdx @@ -0,0 +1,52 @@ +--- +title: Mock functions +--- + +# Mock functions + +Rstest provides some utility functions to help you mock functions. + +## rstest.fn + +Creates a spy on a function. + +```ts +const sayHi = rstest.fn((name: string) => `hi ${name}`); + +const res = sayHi('bob'); + +expect(res).toBe('hi bob'); +``` + +## rstest.spyOn + +Creates a spy on a method of an object. + +```ts +const sayHi = () => 'hi'; +const hi = { + sayHi, +}; + +const spy = rstest.spyOn(hi, 'sayHi'); + +expect(hi.sayHi()).toBe('hi'); + +expect(spy).toHaveBeenCalled(); +``` + +## rstest.isMockFunction + +Determines if the given function is a mocked function. + +## rstest.clearAllMocks + +Clears the `mock.calls`, `mock.instances`, `mock.contexts` and `mock.results` properties of all mocks. + +## rstest.resetAllMocks + +Clears all mocks properties and reset each mock's implementation to its original. + +## rstest.restoreAllMocks + +Reset all mocks and restore original descriptors of spied-on objects. diff --git a/website/docs/en/config/test/_meta.json b/website/docs/en/config/test/_meta.json index 016c08b4..8bc2ccdc 100644 --- a/website/docs/en/config/test/_meta.json +++ b/website/docs/en/config/test/_meta.json @@ -1 +1,8 @@ -["include", "exclude"] +[ + "include", + "exclude", + "includeSource", + "globals", + "setupFiles", + "testEnvironment" +] diff --git a/website/docs/en/config/test/exclude.mdx b/website/docs/en/config/test/exclude.mdx index b96ce956..559fa656 100644 --- a/website/docs/en/config/test/exclude.mdx +++ b/website/docs/en/config/test/exclude.mdx @@ -13,8 +13,8 @@ A list of glob patterns that should be excluded from your test files. Exclude the test files under `node_modules`: -```ts -import { defineConfig } from 'rstest'; +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; export default defineConfig({ include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'], diff --git a/website/docs/en/config/test/globals.mdx b/website/docs/en/config/test/globals.mdx new file mode 100644 index 00000000..027121b9 --- /dev/null +++ b/website/docs/en/config/test/globals.mdx @@ -0,0 +1,46 @@ +--- +overviewHeaders: [] +--- + +# globals + +- **Type:** `boolean` +- **Default:** `false` + +Provide global Rstest APIs for test files, such as `expect`, `test`, `describe`, etc. + +By default, Rstest does not provide global APIs. If you prefer to use the APIs globally like Jest, you can add `globals: true` in the config or pass the `--globals` option to CLI. + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + globals: true, +}); +``` + +## Usage + +When you enable `globals`, you can use the global APIs directly without `import from '@rstest/core'`. + +```diff title="index.test.ts" +- import { describe, expect, it } from '@rstest/core'; + +describe('Index', () => { + it('should add two numbers correctly', () => { + expect(1 + 1).toBe(2); + }); +}); +``` + +### TypeScript support + +To get TypeScript working with the global APIs, add `@rstest/core/globals` to the types field in your `tsconfig.json` + +```json title=tsconfig.json +{ + "compilerOptions": { + "types": ["@rstest/core/globals"] + } +} +``` diff --git a/website/docs/en/config/test/include.mdx b/website/docs/en/config/test/include.mdx index 0814272a..9534f78b 100644 --- a/website/docs/en/config/test/include.mdx +++ b/website/docs/en/config/test/include.mdx @@ -5,8 +5,8 @@ A list of glob patterns that match your test files. -```ts -import { defineConfig } from 'rstest'; +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; export default defineConfig({ include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'], diff --git a/website/docs/en/config/test/includeSource.mdx b/website/docs/en/config/test/includeSource.mdx new file mode 100644 index 00000000..7ee8f3e0 --- /dev/null +++ b/website/docs/en/config/test/includeSource.mdx @@ -0,0 +1,67 @@ +# includeSource + +- **Type:** `string[]` +- **Default:** `[]` + +In-source testing is where the test code lives within the same file as the source code, similar to [Rust's module tests](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest). + +You can define a list of glob patterns that match your in-source test files via `includeSource` configuration. + +```ts +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + includeSource: ['src/**/*.{js,ts}'], +}); +``` + +:::tip +**In-source testing** is usually suitable for small functional functions and utilities, allowing for easy and rapid verification and debugging. For more complex functions and modules, independent test files are recommended. +::: + +### Writing in-source tests + +When `includeSource` defined, Rstest will run all matched files with `import.meta.rstest` inside. + +You can get the Rstest test API via `import.meta.rstest`. + +```ts title=src/helper.ts +export const sayHi = () => 'hi'; + +if (import.meta.rstest) { + const { it, expect } = import.meta.rstest; + it('should test source code correctly', () => { + expect(sayHi()).toBe('hi'); + }); +} +``` + +### For production + +Put the test code inside the `if (import.meta.rstest)` block, and define `import.meta.rstest` as `undefined` in your build configuration (e.g., `rsbuild.config.ts`), which will help the bundler eliminate dead code. + +```diff title=rsbuild.config.ts +import { defineConfig } from '@rsbuild/core'; + +export default defineConfig({ + source: { + define: { ++ 'import.meta.rstest': undefined, + }, + }, +}); +``` + +### TypeScript + +To get TypeScript support for `import.meta.rstest`, you should add `@rstest/core/importMeta` to your `tsconfig.json`: + +```diff title=tsconfig.json +{ + "compilerOptions": { + "types": [ ++ "@rstest/core/importMeta" + ] + } +} +``` diff --git a/website/docs/en/config/test/setupFiles.mdx b/website/docs/en/config/test/setupFiles.mdx new file mode 100644 index 00000000..2a4928a8 --- /dev/null +++ b/website/docs/en/config/test/setupFiles.mdx @@ -0,0 +1,14 @@ +# setupFiles + +- **Type:** `string[]` +- **Default:** `[]` + +A list of paths to modules that run some code to configure or set up the testing environment, they will be run before each test file. + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + setupFiles: ['./scripts/rstest.setup.ts'], +}); +``` diff --git a/website/docs/en/config/test/testEnvironment.mdx b/website/docs/en/config/test/testEnvironment.mdx new file mode 100644 index 00000000..9252dccc --- /dev/null +++ b/website/docs/en/config/test/testEnvironment.mdx @@ -0,0 +1,46 @@ +# testEnvironment + +- **Type:** `'node' | 'jsdom'` +- **Default:** `'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. + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + testEnvironment: 'jsdom', +}); +``` + +### Dom testing + +Rstest supports [jsdom](https://github.com/jsdom/jsdom) for mocking DOM and browser APIs. + +If you want to enable DOM testing, you can use the following configuration: + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + testEnvironment: 'jsdom', +}); +``` + +You also need to install `jsdom`: + +```bash +npm install jsdom -D +``` + +After enabling DOM testing, you can write tests that use browser APIs like `document` and `window`. + +```ts +test('dom test', () => { + document.body.innerHTML = '

hello world

'; + const paragraph = document.querySelector('.content'); + expect(paragraph?.innerHTML).toBe('hello world'); +}); +``` diff --git a/website/docs/en/guide/start/quick-start.mdx b/website/docs/en/guide/start/quick-start.mdx index 7f69197c..fb38d108 100644 --- a/website/docs/en/guide/start/quick-start.mdx +++ b/website/docs/en/guide/start/quick-start.mdx @@ -45,7 +45,7 @@ Rstest has built-in commands such as `watch` and `run`, please refer to [CLI Too ## Writing tests -As a simple example, we have a `sayHi` method. To test it, you can create a test file called `index.test.ts` or use In-Source test similar to [Rust Test](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest). +As a simple example, we have a `sayHi` method. To test it, you can create a test file called `index.test.ts` or use [In-Source test](/config/test/includeSource) similar to [Rust Test](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest). ```ts file=index.ts export const sayHi = () => 'hi'; diff --git a/website/docs/zh/_meta.json b/website/docs/zh/_meta.json index 28b2cb64..2b4a5aeb 100644 --- a/website/docs/zh/_meta.json +++ b/website/docs/zh/_meta.json @@ -8,5 +8,10 @@ "text": "配置", "link": "/config/", "activeMatch": "/config/" + }, + { + "text": "API", + "link": "/api/", + "activeMatch": "/api/" } ] diff --git a/website/docs/zh/api/_meta.json b/website/docs/zh/api/_meta.json new file mode 100644 index 00000000..03890893 --- /dev/null +++ b/website/docs/zh/api/_meta.json @@ -0,0 +1,12 @@ +[ + { + "type": "file", + "name": "index", + "label": "Overview" + }, + { + "type": "dir", + "name": "rstest", + "label": "Rstest Utility" + } +] diff --git a/website/docs/zh/api/index.mdx b/website/docs/zh/api/index.mdx new file mode 100644 index 00000000..5b44da06 --- /dev/null +++ b/website/docs/zh/api/index.mdx @@ -0,0 +1,6 @@ +--- +overview: true +title: API Overview +--- + +当前页面列出了 Rstest 所有的测试 API。 diff --git a/website/docs/zh/api/rstest/index.mdx b/website/docs/zh/api/rstest/index.mdx new file mode 100644 index 00000000..1db10700 --- /dev/null +++ b/website/docs/zh/api/rstest/index.mdx @@ -0,0 +1,25 @@ +--- +title: Rstest Utility +--- + +# Rstest utility + +Rstest 提供了一些实用函数,通过 `rstest` 工具方法可以帮助你更方便地进行测试。 + +你可以直接从 `@rstest/core` 导入 `rstest`,也可以使用它的别名 `rs`。 + +```ts +import { rstest } from '@rstest/core'; + +const fn = rstest.fn(); +fn.mockResolvedValue('foo'); +``` + +```ts +import { rs } from '@rstest/core'; + +const fn = rs.fn(); +fn.mockResolvedValue('foo'); +``` + +或者,你也可以像使用 `jest` 一样全局访问它(当启用了 [globals](/config/test/globals) 配置时)。 diff --git a/website/docs/zh/api/rstest/mockFunctions.mdx b/website/docs/zh/api/rstest/mockFunctions.mdx new file mode 100644 index 00000000..db1c47d8 --- /dev/null +++ b/website/docs/zh/api/rstest/mockFunctions.mdx @@ -0,0 +1,52 @@ +--- +title: Mock functions +--- + +# Mock functions + +Rstest 提供了一些工具方法帮助你进行函数的模拟(mock)。 + +## rstest.fn + +创建一个 mock 函数。 + +```ts +const sayHi = rstest.fn((name: string) => `hi ${name}`); + +const res = sayHi('bob'); + +expect(res).toBe('hi bob'); +``` + +## rstest.spyOn + +对一个对象的方法进行 mock。 + +```ts +const sayHi = () => 'hi'; +const hi = { + sayHi, +}; + +const spy = rstest.spyOn(hi, 'sayHi'); + +expect(hi.sayHi()).toBe('hi'); + +expect(spy).toHaveBeenCalled(); +``` + +## rstest.isMockFunction + +判断给定的函数是否为 mock 函数。 + +## rstest.clearAllMocks + +清除所有 mock 的 `mock.calls`、`mock.instances`、`mock.contexts` 和 `mock.results` 属性。 + +## rstest.resetAllMocks + +清除所有 mock 属性,并将每个 mock 的实现重置为其原始实现。 + +## rstest.restoreAllMocks + +重置所有 mock,并恢复被 mock 的对象的原始描述符。 diff --git a/website/docs/zh/config/test/_meta.json b/website/docs/zh/config/test/_meta.json index 016c08b4..8bc2ccdc 100644 --- a/website/docs/zh/config/test/_meta.json +++ b/website/docs/zh/config/test/_meta.json @@ -1 +1,8 @@ -["include", "exclude"] +[ + "include", + "exclude", + "includeSource", + "globals", + "setupFiles", + "testEnvironment" +] diff --git a/website/docs/zh/config/test/exclude.mdx b/website/docs/zh/config/test/exclude.mdx index e7e28704..1b48f33a 100644 --- a/website/docs/zh/config/test/exclude.mdx +++ b/website/docs/zh/config/test/exclude.mdx @@ -13,8 +13,8 @@ overviewHeaders: [] 排除 `node_modules` 下的测试文件: -```ts -import { defineConfig } from 'rstest'; +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; export default defineConfig({ include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'], diff --git a/website/docs/zh/config/test/globals.mdx b/website/docs/zh/config/test/globals.mdx new file mode 100644 index 00000000..e47e90f8 --- /dev/null +++ b/website/docs/zh/config/test/globals.mdx @@ -0,0 +1,46 @@ +--- +overviewHeaders: [] +--- + +# globals + +- **类型:** `boolean` +- **默认值:** `false` + +为测试文件提供全局的 Rstest API,如 `expect`、`test`、`describe` 等。 + +默认情况下,Rstest 不提供全局 API。如果你想像使用 Jest 一样全局使用这些 API,可以在配置中添加 `globals: true`或者在命令行中传入 `--globals` 选项。 + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + globals: true, +}); +``` + +## 使用 + +当你启用 `globals` 后,就可以直接使用全局 API,而无需 `import from '@rstest/core'`。 + +```diff title="index.test.ts" +- import { describe, expect, it } from '@rstest/core'; + +describe('Index', () => { + it('should add two numbers correctly', () => { + expect(1 + 1).toBe(2); + }); +}); +``` + +### 类型支持 + +为了让 TypeScript 支持全局 API,请在 `tsconfig.json` 的 types 字段中添加 `@rstest/core/globals`。 + +```json title=tsconfig.json +{ + "compilerOptions": { + "types": ["@rstest/core/globals"] + } +} +``` diff --git a/website/docs/zh/config/test/include.mdx b/website/docs/zh/config/test/include.mdx index 45f9ddc0..09088bf3 100644 --- a/website/docs/zh/config/test/include.mdx +++ b/website/docs/zh/config/test/include.mdx @@ -5,8 +5,8 @@ 匹配 glob 规则的文件将被视为测试文件。 -```ts -import { defineConfig } from 'rstest'; +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; export default defineConfig({ include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'], diff --git a/website/docs/zh/config/test/includeSource.mdx b/website/docs/zh/config/test/includeSource.mdx new file mode 100644 index 00000000..92a8f6c6 --- /dev/null +++ b/website/docs/zh/config/test/includeSource.mdx @@ -0,0 +1,67 @@ +# includeSource + +- **类型:** `string[]` +- **默认值:** `[]` + +源码内联测试(In-source testing)指的是测试代码与源代码写在同一个文件中,类似于 [Rust 的模块测试](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest)。 + +你可以通过 `includeSource` 配置,定义一组用于匹配内联测试文件的 glob 模式列表。 + +```ts +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + includeSource: ['src/**/*.{js,ts}'], +}); +``` + +:::tip +源码内联测试通常适用于小型功能函数和工具方法,能够方便地进行快速验证和调试。对于更复杂的功能和模块,建议使用独立的测试文件。 +::: + +### 编写内联测试 + +当定义了 `includeSource` 后,Rstest 会运行所有通过 glob 匹配且包含 `import.meta.rstest` 的文件。 + +你可以通过 `import.meta.rstest` 获取 Rstest 的测试 API。 + +```ts title=src/helper.ts +export const sayHi = () => 'hi'; + +if (import.meta.rstest) { + const { it, expect } = import.meta.rstest; + it('should test source code correctly', () => { + expect(sayHi()).toBe('hi'); + }); +} +``` + +### 生产环境构建 + +将测试代码写在 `if (import.meta.rstest)` 代码块内,并在你的构建配置(如 `rsbuild.config.ts`)中将 `import.meta.rstest` 定义为 `undefined`,这样将有助于打包工具消除无用代码。 + +```diff title=rsbuild.config.ts +import { defineConfig } from '@rsbuild/core'; + +export default defineConfig({ + source: { + define: { ++ 'import.meta.rstest': undefined, + }, + }, +}); +``` + +### TypeScript + +如需让 TypeScript 支持 `import.meta.rstest`,可在 `tsconfig.json` 中添加 `@rstest/core/importMeta`: + +```diff title=tsconfig.json +{ + "compilerOptions": { + "types": [ ++ "@rstest/core/importMeta" + ] + } +} +``` diff --git a/website/docs/zh/config/test/setupFiles.mdx b/website/docs/zh/config/test/setupFiles.mdx new file mode 100644 index 00000000..7319d1aa --- /dev/null +++ b/website/docs/zh/config/test/setupFiles.mdx @@ -0,0 +1,14 @@ +# setupFiles + +- **类型:** `string[]` +- **默认值:** `[]` + +一组 setup 文件列表,通常用于配置或设置测试环境,它们将在每个测试文件执行前运行。 + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + setupFiles: ['./scripts/rstest.setup.ts'], +}); +``` diff --git a/website/docs/zh/config/test/testEnvironment.mdx b/website/docs/zh/config/test/testEnvironment.mdx new file mode 100644 index 00000000..2c693ee6 --- /dev/null +++ b/website/docs/zh/config/test/testEnvironment.mdx @@ -0,0 +1,46 @@ +# testEnvironment + +- **类型:** `'node' | 'jsdom'` +- **默认值:** `'node'` + +测试时所使用的环境。 + +Rstest 默认使用 Node.js 作为测试环境。如果你在开发 Web 应用,可以使用类浏览器环境,如 `jsdom`。 + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + testEnvironment: 'jsdom', +}); +``` + +### DOM 测试 + +Rstest 支持使用 [jsdom](https://github.com/jsdom/jsdom) 来模拟 DOM 和浏览器 API。 + +如果你想启用 DOM 测试,可以使用如下配置: + +```ts title="rstest.config.ts" +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + testEnvironment: 'jsdom', +}); +``` + +你还需要安装 `jsdom`: + +```bash +npm install jsdom -D +``` + +启用 DOM 测试后,你可以在测试用例中使用 `document` 和 `window` 等浏览器 API。 + +```ts +test('dom test', () => { + document.body.innerHTML = '

hello world

'; + const paragraph = document.querySelector('.content'); + expect(paragraph?.innerHTML).toBe('hello world'); +}); +``` diff --git a/website/docs/zh/guide/start/quick-start.mdx b/website/docs/zh/guide/start/quick-start.mdx index 687dccdb..870e986e 100644 --- a/website/docs/zh/guide/start/quick-start.mdx +++ b/website/docs/zh/guide/start/quick-start.mdx @@ -45,7 +45,7 @@ Rstest 内置了 `watch`、`run` 等命令,请参考 [CLI 工具](/guide/basic ## 编写测试 -作为一个简单的例子,我们有一个 `sayHi` 方法。为了对它进行测试,你可以创建一个名为 `index.test.ts` 的测试文件或使用与 [Rust 测试](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest) 类似的 In-Source 测试。 +作为一个简单的例子,我们有一个 `sayHi` 方法。为了对它进行测试,你可以创建一个名为 `index.test.ts` 的测试文件或使用与 [Rust 测试](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest) 类似的 [In-Source 测试](/config/test/includeSource)。 ```ts file=index.ts export const sayHi = () => 'hi'; 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