From fc1ee00094d85cc78beeda8f6a034d85e2ffd8ac Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 10 Oct 2025 19:16:30 +0800 Subject: [PATCH 1/6] test: align more test cases with webpack --- .../rspack-test-tools/etc/test-tools.api.md | 6 + packages/rspack-test-tools/src/case/index.ts | 7 +- packages/rspack-test-tools/src/case/normal.ts | 38 ++++- .../rspack-test-tools/src/helper/directory.ts | 4 +- .../rspack-test-tools/src/test/creator.ts | 4 +- tests/rspack-test/Normal-dev.test.js | 13 ++ tests/rspack-test/Normal-prod.test.js | 19 +++ .../compilerCases/banner-plugin.js | 80 ++++++++++ .../compilerCases/changes-and-removals.js | 131 +++++++++++++++ .../rspack-test/compilerCases/watch-close.js | 47 ++++++ .../compilerCases/watch-detection.js | 151 ++++++++++++++++++ .../rspack-test/compilerCases/watch-events.js | 30 ++++ tests/rspack-test/compilerCases/watch.js | 96 +++++++++++ .../watch/node_modules/inline-loader/index.js | 3 + .../node_modules/inline-loader/package.json | 4 + .../node_modules/nested-package1/index.js | 0 .../node_modules/nested-package1/package.json | 4 + .../watch/node_modules/package/index.js | 4 + .../node_modules/nested-package2/index.js | 0 .../node_modules/nested-package2/package.json | 4 + .../watch/node_modules/package/package.json | 4 + .../watch/node_modules/some-loader/index.js | 3 + .../node_modules/some-loader/package.json | 4 + .../multiCompilerCases/watch-events.js | 29 ++++ .../parsing/issue-7519/test.config.js | 2 +- 25 files changed, 674 insertions(+), 13 deletions(-) create mode 100644 tests/rspack-test/Normal-dev.test.js create mode 100644 tests/rspack-test/Normal-prod.test.js create mode 100644 tests/rspack-test/compilerCases/banner-plugin.js create mode 100644 tests/rspack-test/compilerCases/changes-and-removals.js create mode 100644 tests/rspack-test/compilerCases/watch-close.js create mode 100644 tests/rspack-test/compilerCases/watch-detection.js create mode 100644 tests/rspack-test/compilerCases/watch-events.js create mode 100644 tests/rspack-test/compilerCases/watch.js create mode 100644 tests/rspack-test/fixtures/watch/node_modules/inline-loader/index.js create mode 100644 tests/rspack-test/fixtures/watch/node_modules/inline-loader/package.json create mode 100644 tests/rspack-test/fixtures/watch/node_modules/nested-package1/index.js create mode 100644 tests/rspack-test/fixtures/watch/node_modules/nested-package1/package.json create mode 100644 tests/rspack-test/fixtures/watch/node_modules/package/index.js create mode 100644 tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/index.js create mode 100644 tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/package.json create mode 100644 tests/rspack-test/fixtures/watch/node_modules/package/package.json create mode 100644 tests/rspack-test/fixtures/watch/node_modules/some-loader/index.js create mode 100644 tests/rspack-test/fixtures/watch/node_modules/some-loader/package.json create mode 100644 tests/rspack-test/multiCompilerCases/watch-events.js diff --git a/packages/rspack-test-tools/etc/test-tools.api.md b/packages/rspack-test-tools/etc/test-tools.api.md index 7d886f34dfea..122df84a306d 100644 --- a/packages/rspack-test-tools/etc/test-tools.api.md +++ b/packages/rspack-test-tools/etc/test-tools.api.md @@ -92,6 +92,9 @@ export function createConfigCase(name: string, src: string, dist: string): void; // @public (undocumented) export function createDefaultsCase(name: string, src: string): void; +// @public (undocumented) +export function createDevNormalCase(name: string, src: string, dist: string): void; + // @public (undocumented) export function createDiagnosticCase(name: string, src: string, dist: string): void; @@ -134,6 +137,9 @@ export function createNativeWatcher(name: string, src: string, dist: string, tem // @public (undocumented) export function createNormalCase(name: string, src: string, dist: string): void; +// @public (undocumented) +export function createProdNormalCase(name: string, src: string, dist: string): void; + // @public (undocumented) export function createSerialCase(name: string, src: string, dist: string): void; diff --git a/packages/rspack-test-tools/src/case/index.ts b/packages/rspack-test-tools/src/case/index.ts index cfac4bd6b957..1f5071d7ef99 100644 --- a/packages/rspack-test-tools/src/case/index.ts +++ b/packages/rspack-test-tools/src/case/index.ts @@ -26,7 +26,12 @@ export { export type { TMultiCompilerCaseConfig } from "./multi-compiler"; export { createMultiCompilerCase } from "./multi-compiler"; export { createNativeWatcher } from "./native-watcher"; -export { createHotNormalCase, createNormalCase } from "./normal"; +export { + createDevNormalCase, + createHotNormalCase, + createNormalCase, + createProdNormalCase +} from "./normal"; export { createSerialCase } from "./serial"; export type { TStatsAPICaseConfig } from "./stats-api"; export { createStatsAPICase } from "./stats-api"; diff --git a/packages/rspack-test-tools/src/case/normal.ts b/packages/rspack-test-tools/src/case/normal.ts index ac5e6dcd2976..22af771aa2d4 100644 --- a/packages/rspack-test-tools/src/case/normal.ts +++ b/packages/rspack-test-tools/src/case/normal.ts @@ -21,7 +21,8 @@ const NORMAL_CASES_ROOT = path.resolve( ); const createCaseOptions = ( - hot: boolean + hot: boolean, + mode?: "development" | "production" ): IBasicCaseCreatorOptions => { return { clean: true, @@ -30,9 +31,13 @@ const createCaseOptions = ( { config: async (context: ITestContext) => { const compiler = getCompiler(context, name); - let options = defaultOptions(context, { - plugins: hot ? [new HotModuleReplacementPlugin()] : [] - }); + let options = defaultOptions( + context, + { + plugins: hot ? [new HotModuleReplacementPlugin()] : [] + }, + mode + ); options = await config( context, name, @@ -74,6 +79,20 @@ export function createHotNormalCase(name: string, src: string, dist: string) { hotCreator.create(name, src, dist); } +const devCreator = new BasicCaseCreator( + createCaseOptions(false, "development") +); +export function createDevNormalCase(name: string, src: string, dist: string) { + devCreator.create(name, src, dist); +} + +const prodCreator = new BasicCaseCreator( + createCaseOptions(false, "production") +); +export function createProdNormalCase(name: string, src: string, dist: string) { + prodCreator.create(name, src, dist); +} + function findBundle( context: ITestContext, options: TCompilerOptions @@ -90,7 +109,8 @@ function findBundle( function defaultOptions( context: ITestContext, - compilerOptions: TCompilerOptions + compilerOptions: TCompilerOptions, + mode?: "development" | "production" ) { let testConfig: TCompilerOptions = {}; const testConfigPath = path.join(context.getSource(), "test.config.js"); @@ -107,11 +127,12 @@ function defaultOptions( entry: `./${path.relative(NORMAL_CASES_ROOT, context.getSource())}/`, target: compilerOptions?.target || "async-node", devtool: compilerOptions?.devtool, - mode: compilerOptions?.mode || "none", + mode: compilerOptions?.mode || mode || "none", optimization: compilerOptions?.mode ? { - // emitOnErrors: true, + emitOnErrors: true, minimizer: [terserForTesting], + minimize: false, ...testConfig.optimization } : { @@ -125,7 +146,7 @@ function defaultOptions( usedExports: true, mangleExports: true, // CHANGE: rspack does not support `emitOnErrors` yet. - // emitOnErrors: true, + emitOnErrors: true, concatenateModules: !!testConfig?.optimization?.concatenateModules, innerGraph: true, // CHANGE: size is not supported yet @@ -133,6 +154,7 @@ function defaultOptions( // chunkIds: "size", moduleIds: "named", chunkIds: "named", + minimize: false, minimizer: [terserForTesting], ...compilerOptions?.optimization }, diff --git a/packages/rspack-test-tools/src/helper/directory.ts b/packages/rspack-test-tools/src/helper/directory.ts index 2af6fdf85615..78dc89c5b1eb 100644 --- a/packages/rspack-test-tools/src/helper/directory.ts +++ b/packages/rspack-test-tools/src/helper/directory.ts @@ -40,7 +40,9 @@ export function describeByWalk( if (options.exclude) { if ( options.exclude.some(exclude => { - return exclude.test(folder); + return exclude.test( + path.join(dirname, folder).replace(/\\/g, "/") + ); }) ) { return false; diff --git a/packages/rspack-test-tools/src/test/creator.ts b/packages/rspack-test-tools/src/test/creator.ts index ae7f963f6516..4fc95c858e52 100644 --- a/packages/rspack-test-tools/src/test/creator.ts +++ b/packages/rspack-test-tools/src/test/creator.ts @@ -170,7 +170,7 @@ export class BasicCaseCreator { cb(e); }); }, - options.timeout || 300000 + options.timeout || 3000 ); chain = chain.then( @@ -260,7 +260,7 @@ export class BasicCaseCreator { ); } }, - options.timeout || 60000 + options.timeout || 6000 ); const env = this.createEnv(testConfig, options); } diff --git a/tests/rspack-test/Normal-dev.test.js b/tests/rspack-test/Normal-dev.test.js new file mode 100644 index 000000000000..8e9c987477d6 --- /dev/null +++ b/tests/rspack-test/Normal-dev.test.js @@ -0,0 +1,13 @@ +const { createDevNormalCase, describeByWalk } = require("@rspack/test-tools"); +const path = require("path"); + +describeByWalk(__filename, (name, src, dist) => { + createDevNormalCase(name, src, dist); +}, { + source: path.resolve(__dirname, "./normalCases"), + dist: path.resolve(__dirname, `./js/normal-dev`), + exclude: [ + /parsing\/harmony-deep-exports/, + /parsing\/asi/ + ] +}); diff --git a/tests/rspack-test/Normal-prod.test.js b/tests/rspack-test/Normal-prod.test.js new file mode 100644 index 000000000000..ddd110cb4123 --- /dev/null +++ b/tests/rspack-test/Normal-prod.test.js @@ -0,0 +1,19 @@ +const { createProdNormalCase, describeByWalk } = require("@rspack/test-tools"); +const path = require("path"); + +describeByWalk(__filename, (name, src, dist) => { + createProdNormalCase(name, src, dist); +}, { + source: path.resolve(__dirname, "./normalCases"), + dist: path.resolve(__dirname, `./js/normal-prod`), + // FIXME: these cases throw errors in production + exclude: [ + /warnings\/require-as-expression/, + /side-effects\/empty-modules/, + /parsing\/resolve-weak-context/, + /parsing\/issue-7519/, + /parsing\/api/, + /mjs\/cjs-import-default/, + /inner-graph\/simple/ + ] +}); diff --git a/tests/rspack-test/compilerCases/banner-plugin.js b/tests/rspack-test/compilerCases/banner-plugin.js new file mode 100644 index 000000000000..fb94053601cf --- /dev/null +++ b/tests/rspack-test/compilerCases/banner-plugin.js @@ -0,0 +1,80 @@ +const rspack = require("@rspack/core"); +const fs = require("fs"); + +let lastStats = null; +/** @type {import('@rspack/test-tools').TCompilerCaseConfig[]} */ +module.exports = [{ + description: "should cache assets", + options(context) { + return { + mode: "development", + entry: { + entry1: context.getDist("entry1.js"), + entry2: context.getDist("entry2.js") + }, + output: { + path: context.getDist("dist") + }, + plugins: [new rspack.BannerPlugin("banner is a string")] + }; + }, + async compiler(context, compiler) { + compiler.outputFileSystem = fs; + fs.writeFileSync(context.getDist("entry1.js"), "1", "utf-8"); + fs.writeFileSync(context.getDist("entry2.js"), "1", "utf-8"); + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + if (err) return reject(err); + try { + const footerFileResults = fs.readFileSync(context.getDist("dist/entry1.js"), "utf8").split("\n"); + expect(footerFileResults[0]).toBe("/*! banner is a string */"); + fs.writeFileSync(context.getDist("entry2.js"), "2", "utf-8"); + compiler.run((err, stats) => { + if (err) return reject(err); + lastStats = stats; + resolve(); + }, { + modifiedFiles: new Set([context.getDist("entry2.js")]) + }); + } catch (err) { + reject(err); + } + + }); + }); + }, + async check() { + const { assets } = lastStats.toJson(); + expect(assets.find(as => as.name === "entry1.js").emitted).toBe(false); + expect(assets.find(as => as.name === "entry2.js").emitted).toBe(true); + } +}, { + description: "can place banner as footer", + options(context) { + return { + mode: "development", + entry: { + footerFile: context.getDist("footerFile.js") + }, + output: { + path: context.getDist("dist") + }, + plugins: [ + new rspack.BannerPlugin({ + banner: "banner is a string", + footer: true + }) + ] + }; + }, + async compiler(context, compiler) { + compiler.outputFileSystem = fs; + fs.writeFileSync(context.getDist("footerFile.js"), "footer", "utf-8"); + }, + async check({ context }) { + const footerFileResults = fs.readFileSync(context.getDist("dist/footerFile.js"), "utf8").split("\n"); + expect(footerFileResults.pop()).toBe("/*! banner is a string */"); + } +}]; diff --git a/tests/rspack-test/compilerCases/changes-and-removals.js b/tests/rspack-test/compilerCases/changes-and-removals.js new file mode 100644 index 000000000000..e55c250ff122 --- /dev/null +++ b/tests/rspack-test/compilerCases/changes-and-removals.js @@ -0,0 +1,131 @@ +const fs = require("fs"); + +const getChanges = (compiler) => { + const modifiedFiles = compiler.modifiedFiles; + const removedFiles = compiler.removedFiles; + return { + removed: removedFiles && [...removedFiles], + modified: modifiedFiles && [...modifiedFiles] + }; +}; + +let changes = null; +/** @type {import('@rspack/test-tools').TCompilerCaseConfig[]} */ +module.exports = [{ + description: "should not track modified/removed files during initial watchRun", + options(context) { + changes = null; + return { + entry: context.getDist("temp-file.js"), + output: { + path: context.getDist("dist"), + filename: "bundle.js" + }, + }; + }, + async compiler(context, compiler) { + compiler.hooks.watchRun.tap("ChangesAndRemovalsTest", (compiler) => { + changes = getChanges(compiler); + }); + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + const watcher = compiler.watch({ aggregateTimeout: 200 }, (err) => { + if (err) reject(err); + watcher.close(resolve); + }); + }); + }, + async check() { + expect(changes).toEqual({ + removed: [], + modified: [] + }); + } +}, { + description: "should track modified files when they've been modified", + options(context) { + changes = null; + return { + entry: context.getDist("temp-file.js"), + output: { + path: context.getDist("dist"), + filename: "bundle.js" + }, + }; + }, + async build(context, compiler) { + let firstWatchRun = true; + let firstDone = true; + return new Promise((resolve, reject) => { + let watcher = null; + compiler.hooks.watchRun.tap("ChangesAndRemovalsTest", (compiler) => { + if (firstWatchRun) { + firstWatchRun = false; + return; + } + changes = getChanges(compiler); + watcher.close(resolve); + }); + compiler.hooks.done.tap("ChangesAndRemovalsTest", () => { + if (!firstDone) return; + firstDone = false; + setTimeout(() => { + fs.appendFileSync(context.getDist("temp-file.js"), "\nlet x = 'file modified';"); + }, 300); + }); + watcher = compiler.watch({ aggregateTimeout: 200 }, (err) => { + if (err) reject(err); + }); + }); + }, + async check({ context }) { + expect(changes).toEqual({ + removed: [], + modified: [context.getDist("temp-file.js")] + }); + } +}, { + description: "should track removed file when removing file", + options(context) { + changes = null; + return { + entry: context.getDist("temp-file.js"), + output: { + path: context.getDist("dist"), + filename: "bundle.js" + }, + }; + }, + async build(context, compiler) { + let firstWatchRun = true; + let firstDone = true; + return new Promise((resolve, reject) => { + let watcher = null; + compiler.hooks.watchRun.tap("ChangesAndRemovalsTest", (compiler) => { + if (firstWatchRun) { + firstWatchRun = false; + return; + } + changes = getChanges(compiler); + watcher.close(resolve); + }); + compiler.hooks.done.tap("ChangesAndRemovalsTest", () => { + if (!firstDone) return; + firstDone = false; + setTimeout(() => { + fs.unlinkSync(context.getDist("temp-file.js")); + }, 300); + }); + watcher = compiler.watch({ aggregateTimeout: 200 }, (err) => { + if (err) reject(err); + }); + }); + }, + async check({ context }) { + expect(changes).toEqual({ + removed: [context.getDist("temp-file.js")], + modified: [] + }); + } +}]; diff --git a/tests/rspack-test/compilerCases/watch-close.js b/tests/rspack-test/compilerCases/watch-close.js new file mode 100644 index 000000000000..690d86f1c016 --- /dev/null +++ b/tests/rspack-test/compilerCases/watch-close.js @@ -0,0 +1,47 @@ + + +let watcher = null; + +function close(watcher, callback) { + return new Promise(res => { + const onClose = () => { + callback(); + res(); + }; + watcher.close(onClose); + }); +} +/** @type {import('@rspack/test-tools').TCompilerCaseConfig} */ +module.exports = { + description: "multiple calls watcher: each callback should be called", + options(context) { + return { + mode: "development", + entry: "./a.js", + output: { + filename: "bundle.js" + } + }; + }, + async compiler(context, compiler) { + + }, + async build(context, compiler) { + watcher = compiler.watch({ poll: 300 }, () => { }); + + }, + async check() { + let num = 0; + + await Promise.all([ + close(watcher, () => (num += 1)), + close(watcher, () => (num += 10)) + ]); + await Promise.all([ + close(watcher, () => (num += 100)), + close(watcher, () => (num += 1000)) + ]); + + expect(num).toBe(1111); + } +} diff --git a/tests/rspack-test/compilerCases/watch-detection.js b/tests/rspack-test/compilerCases/watch-detection.js new file mode 100644 index 000000000000..34d5c0469ebc --- /dev/null +++ b/tests/rspack-test/compilerCases/watch-detection.js @@ -0,0 +1,151 @@ +const path = require("path"); +const fs = require("fs"); +const { createFsFromVolume, Volume } = require("memfs"); + +function createTestCase(changeTimeout, invalidate) { + const fixturePath = path.join(__dirname, "../fixtures", "temp-" + changeTimeout); + const filePath = path.join(fixturePath, "file.js"); + const file2Path = path.join(fixturePath, "file2.js"); + const loaderPath = path.join(__dirname, "../fixtures", "delay-loader.js"); + const memfs = createFsFromVolume(new Volume()); + + return { + description: `time between changes ${changeTimeout}ms${invalidate ? " with invalidate call" : ""}`, + options(context) { + try { + fs.mkdirSync(fixturePath); + } catch (e) { + // empty + } + fs.writeFileSync(filePath, "require('./file2')", "utf-8"); + fs.writeFileSync(file2Path, "original", "utf-8"); + return { + mode: "development", + entry: loaderPath + "!" + filePath, + output: { + path: "/directory", + filename: "bundle.js" + } + }; + }, + async compiler(context, compiler) { + compiler.outputFileSystem = memfs; + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + try { + let onChange; + compiler.hooks.done.tap("WatchDetectionTest", () => { + try { + if (onChange) onChange(); + } catch (e) { + console.error(e); + } + }); + let watcher; + + step1(); + + function step1() { + onChange = () => { + if ( + memfs.readFileSync("/directory/bundle.js") && + memfs + .readFileSync("/directory/bundle.js") + .toString() + .indexOf("original") >= 0 + ) + step2(); + }; + + watcher = compiler.watch( + { + aggregateTimeout: 50 + }, + () => { } + ); + } + + function step2() { + onChange = () => { + expect(compiler.modifiedFiles).not.toBe(undefined); + expect(compiler.removedFiles).not.toBe(undefined); + }; + + fs.writeFile( + filePath, + "require('./file2'); again", + "utf-8", + handleError + ); + + setTimeout(step3, changeTimeout); + } + + function step3() { + if (invalidate) watcher.invalidate(); + fs.writeFile(file2Path, "wrong", "utf-8", handleError); + + setTimeout(step4, changeTimeout); + } + + function step4() { + onChange = () => { + expect(compiler.modifiedFiles).not.toBe(undefined); + expect(compiler.removedFiles).not.toBe(undefined); + if ( + memfs + .readFileSync("/directory/bundle.js") + .toString() + .indexOf("correct") >= 0 + ) + step5(); + }; + + fs.writeFile(file2Path, "correct", "utf-8", handleError); + } + + function step5() { + onChange = null; + + watcher.close(() => { + setTimeout(resolve, 100); + }); + } + + function handleError(err) { + if (err) reject(err); + } + } catch (e) { + console.error(e); + reject(e); + } + }); + }, + check() { + try { + fs.unlinkSync(filePath); + } catch (e) { + // empty + } + try { + fs.unlinkSync(file2Path); + } catch (e) { + // empty + } + try { + fs.rmdirSync(fixturePath); + } catch (e) { + // empty + } + } + }; +} + +/** @type {import('@rspack/test-tools').TCompilerCaseConfig[]} */ +module.exports = [ + createTestCase(500, true), + createTestCase(500, false), + createTestCase(10, true), + createTestCase(10, false) +]; \ No newline at end of file diff --git a/tests/rspack-test/compilerCases/watch-events.js b/tests/rspack-test/compilerCases/watch-events.js new file mode 100644 index 000000000000..d139d5f6e819 --- /dev/null +++ b/tests/rspack-test/compilerCases/watch-events.js @@ -0,0 +1,30 @@ + +let called = false; + +/** @type {import('@rspack/test-tools').TCompilerCaseConfig} */ +module.exports = [{ + description: "should emit 'watch-close' when using single-compiler mode and the compiler is not running", + options(context) { + return { + entry: "./a.js" + }; + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + const watcher = compiler.watch({}, (err, stats) => { + if (err) return reject(err); + resolve(); + }); + compiler.hooks.watchClose.tap("WatcherEventsTest", () => { + called = true; + }); + + compiler.hooks.done.tap("WatcherEventsTest", () => { + watcher.close(); + }); + }); + }, + async check() { + expect(called).toBe(true); + } +}] diff --git a/tests/rspack-test/compilerCases/watch.js b/tests/rspack-test/compilerCases/watch.js new file mode 100644 index 000000000000..088134038b76 --- /dev/null +++ b/tests/rspack-test/compilerCases/watch.js @@ -0,0 +1,96 @@ +let counterBeforeCompile = 0; +let counterDone = 0; +let counterHandler = 0; +let calls = 0; + +/** @type {import('@rspack/test-tools').TCompilerCaseConfig} */ +module.exports = [{ + description: "should only compile a single time", + options(context) { + return { + context: context.getSource("watch"), + watch: true, + mode: "development", + experiments: { + futureDefaults: true + }, + entry: "./src/index.js", + module: { + rules: [ + { + test: /\.js$/, + use: "some-loader" + } + ] + }, + plugins: [ + c => { + c.hooks.beforeCompile.tap("test", () => { + counterBeforeCompile++; + }); + c.hooks.done.tap("test", () => { + counterDone++; + }); + } + ] + }; + }, + compilerCallback(err, stats) { + if (err) throw err; + if (stats.hasErrors()) throw new Error(stats.toString()); + counterHandler++; + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + compiler.hooks.afterDone.tap("afterDoneRunTest", () => { + compiler.close(resolve); + }); + }); + }, + async check() { + expect(counterBeforeCompile).toBe(1); + expect(counterDone).toBe(1); + expect(counterHandler).toBe(1); + } +}, { + description: "should correctly emit asset when invalidation occurs again", + options(context) { + return { + mode: "development", + context: context.getSource("watch"), + entry: "./src/index.js", + }; + }, + async compiler(context, compiler) { + compiler.hooks.emit.tap("Test", () => { + calls++; + }); + // Ensure the second invalidation can occur during compiler running + let once = false; + compiler.hooks.afterCompile.tapAsync("LongTask", (_, cb) => { + if (once) return cb(); + once = true; + setTimeout(() => { + cb(); + }, 1000); + }); + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + compiler.watch({}, (err, stats) => { + if (err) return reject(err); + }); + compiler.watching.invalidate(); + setTimeout(() => { + compiler.watching.invalidate(); + }, 50); + compiler.hooks.done.tap("Test", () => { + compiler.close(resolve); + resolve(); + }); + }); + }, + async check() { + expect(calls).toBe(2); + } +}]; diff --git a/tests/rspack-test/fixtures/watch/node_modules/inline-loader/index.js b/tests/rspack-test/fixtures/watch/node_modules/inline-loader/index.js new file mode 100644 index 000000000000..d444337558ef --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/inline-loader/index.js @@ -0,0 +1,3 @@ +module.exports = function(x) { + return x; +} diff --git a/tests/rspack-test/fixtures/watch/node_modules/inline-loader/package.json b/tests/rspack-test/fixtures/watch/node_modules/inline-loader/package.json new file mode 100644 index 000000000000..4f7472457107 --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/inline-loader/package.json @@ -0,0 +1,4 @@ +{ + "name": "inline-loader", + "version": "1.0.0" +} diff --git a/tests/rspack-test/fixtures/watch/node_modules/nested-package1/index.js b/tests/rspack-test/fixtures/watch/node_modules/nested-package1/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/rspack-test/fixtures/watch/node_modules/nested-package1/package.json b/tests/rspack-test/fixtures/watch/node_modules/nested-package1/package.json new file mode 100644 index 000000000000..77e1c5ddfead --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/nested-package1/package.json @@ -0,0 +1,4 @@ +{ + "name": "nested-package1", + "version": "1.0.0" +} diff --git a/tests/rspack-test/fixtures/watch/node_modules/package/index.js b/tests/rspack-test/fixtures/watch/node_modules/package/index.js new file mode 100644 index 000000000000..e1e724de5326 --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/package/index.js @@ -0,0 +1,4 @@ +import "nested-package1"; +import "nested-package2"; +import "inline-loader!nested-package1"; +import "inline-loader!nested-package2"; diff --git a/tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/index.js b/tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/package.json b/tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/package.json new file mode 100644 index 000000000000..99b7bef613e0 --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/package/node_modules/nested-package2/package.json @@ -0,0 +1,4 @@ +{ + "name": "nested-package2", + "version": "1.0.0" +} diff --git a/tests/rspack-test/fixtures/watch/node_modules/package/package.json b/tests/rspack-test/fixtures/watch/node_modules/package/package.json new file mode 100644 index 000000000000..75b93e3b25af --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/package/package.json @@ -0,0 +1,4 @@ +{ + "name": "package", + "version": "1.0.0" +} diff --git a/tests/rspack-test/fixtures/watch/node_modules/some-loader/index.js b/tests/rspack-test/fixtures/watch/node_modules/some-loader/index.js new file mode 100644 index 000000000000..d444337558ef --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/some-loader/index.js @@ -0,0 +1,3 @@ +module.exports = function(x) { + return x; +} diff --git a/tests/rspack-test/fixtures/watch/node_modules/some-loader/package.json b/tests/rspack-test/fixtures/watch/node_modules/some-loader/package.json new file mode 100644 index 000000000000..f05476029b9e --- /dev/null +++ b/tests/rspack-test/fixtures/watch/node_modules/some-loader/package.json @@ -0,0 +1,4 @@ +{ + "name": "some-loader", + "version": "1.0.0" +} diff --git a/tests/rspack-test/multiCompilerCases/watch-events.js b/tests/rspack-test/multiCompilerCases/watch-events.js new file mode 100644 index 000000000000..7713552553c9 --- /dev/null +++ b/tests/rspack-test/multiCompilerCases/watch-events.js @@ -0,0 +1,29 @@ +let called = 0; +/** @type {import('@rspack/test-tools').TMultiCompilerCaseConfig} */ +module.exports = [{ + description: "should emit 'watch-close' when using multi-compiler mode and the compiler is not running", + options(context) { + return [{ + entry: "./a.js" + }]; + }, + async build(context, compiler) { + return new Promise((resolve, reject) => { + const watcher = compiler.watch({}, (err, stats) => { + if (err) return reject(err); + resolve(); + }); + + compiler.hooks.watchClose.tap("WatcherEventsTest", () => { + called = true; + }); + + compiler.hooks.done.tap("WatcherEventsTest", () => { + watcher.close(); + }); + }); + }, + async check() { + expect(called).toBe(true); + } +}]; diff --git a/tests/rspack-test/normalCases/parsing/issue-7519/test.config.js b/tests/rspack-test/normalCases/parsing/issue-7519/test.config.js index 48ccd155525b..650e3b6756e7 100644 --- a/tests/rspack-test/normalCases/parsing/issue-7519/test.config.js +++ b/tests/rspack-test/normalCases/parsing/issue-7519/test.config.js @@ -6,7 +6,7 @@ const config = { }, plugins: [ new rspack.DefinePlugin({ - 'process.env.NODE_ENV': "'development'", + 'process.env.NODE_ENV': "\"development\"", }) ] }; From a6e9e692017b18b5ce0f8d2d3ff0a2532762e295 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 10 Oct 2025 19:19:39 +0800 Subject: [PATCH 2/6] test: align more test cases with webpack --- packages/rspack-test-tools/src/test/creator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rspack-test-tools/src/test/creator.ts b/packages/rspack-test-tools/src/test/creator.ts index 4fc95c858e52..ae7f963f6516 100644 --- a/packages/rspack-test-tools/src/test/creator.ts +++ b/packages/rspack-test-tools/src/test/creator.ts @@ -170,7 +170,7 @@ export class BasicCaseCreator { cb(e); }); }, - options.timeout || 3000 + options.timeout || 300000 ); chain = chain.then( @@ -260,7 +260,7 @@ export class BasicCaseCreator { ); } }, - options.timeout || 6000 + options.timeout || 60000 ); const env = this.createEnv(testConfig, options); } From 7f403c37b15e4741eb321ef9064d0884df2246da Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Sat, 11 Oct 2025 11:06:24 +0800 Subject: [PATCH 3/6] test: align more test cases with webpack --- tests/rspack-test/compilerCases/invalidation-multi-times.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rspack-test/compilerCases/invalidation-multi-times.js b/tests/rspack-test/compilerCases/invalidation-multi-times.js index 10c9df2dab69..8092bb4b2f87 100644 --- a/tests/rspack-test/compilerCases/invalidation-multi-times.js +++ b/tests/rspack-test/compilerCases/invalidation-multi-times.js @@ -34,7 +34,7 @@ module.exports = { compiler.watching.invalidateWithChangesAndRemovals(new Set([path.resolve(__dirname, "../fixtures/b.js")])); setTimeout(() => { resolve(); - }, 500) + }, 2000) } }); }); From eb077e99b007e2470f4ddecb54167f12ec603df1 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Sat, 11 Oct 2025 11:18:22 +0800 Subject: [PATCH 4/6] test: align more test cases with webpack --- packages/rspack-test-tools/src/test/creator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rspack-test-tools/src/test/creator.ts b/packages/rspack-test-tools/src/test/creator.ts index ae7f963f6516..877615a277ac 100644 --- a/packages/rspack-test-tools/src/test/creator.ts +++ b/packages/rspack-test-tools/src/test/creator.ts @@ -355,7 +355,6 @@ export class BasicCaseCreator { protected clean(folders: string[]) { for (const f of folders) { rimrafSync(f); - fs.mkdirSync(f, { recursive: true }); } } From 7422dc1e4ab27c97100111630ff9021634d3cb24 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Sat, 11 Oct 2025 11:19:02 +0800 Subject: [PATCH 5/6] test: align more test cases with webpack --- tests/rspack-test/compilerCases/watch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/rspack-test/compilerCases/watch.js b/tests/rspack-test/compilerCases/watch.js index 088134038b76..a3beb1997470 100644 --- a/tests/rspack-test/compilerCases/watch.js +++ b/tests/rspack-test/compilerCases/watch.js @@ -86,7 +86,6 @@ module.exports = [{ }, 50); compiler.hooks.done.tap("Test", () => { compiler.close(resolve); - resolve(); }); }); }, From 20d25d4bd4355cc4338080aaa64122969e4e1004 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Sat, 11 Oct 2025 11:49:06 +0800 Subject: [PATCH 6/6] test: align more test cases with webpack --- packages/rspack-test-tools/src/helper/hot-update/plugin.ts | 6 +++++- .../configCases/compilation/add-include/rspack.config.js | 3 +++ .../configCases/hooks/should-emit-2/rspack.config.js | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/rspack-test-tools/src/helper/hot-update/plugin.ts b/packages/rspack-test-tools/src/helper/hot-update/plugin.ts index 06a345622dea..c1e658f61f2f 100644 --- a/packages/rspack-test-tools/src/helper/hot-update/plugin.ts +++ b/packages/rspack-test-tools/src/helper/hot-update/plugin.ts @@ -84,7 +84,11 @@ export class HotUpdatePlugin { return; } this.initialized = true; - await fs.rmdir(this.tempDir, { recursive: true }); + try { + await fs.rmdir(this.tempDir, { recursive: true }); + } catch (_e) { + // empty + } await fs.cp(this.projectDir, this.tempDir, { recursive: true }); await loopFile(this.tempDir, (filePath, content) => { const contents = content.split(/---+\r?\n/g); diff --git a/tests/rspack-test/configCases/compilation/add-include/rspack.config.js b/tests/rspack-test/configCases/compilation/add-include/rspack.config.js index a77b7d06fc6d..90223d3fd4cd 100644 --- a/tests/rspack-test/configCases/compilation/add-include/rspack.config.js +++ b/tests/rspack-test/configCases/compilation/add-include/rspack.config.js @@ -82,6 +82,9 @@ class Plugin { const moduleId = compilation.chunkGraph.getModuleId(module); manifest[key] = moduleId; } + if (!fs.existsSync(compiler.outputPath)) { + fs.mkdirSync(compiler.outputPath, { recursive: true }); + } fs.writeFileSync( path.join(compiler.outputPath, "manifest.json"), JSON.stringify(manifest), diff --git a/tests/rspack-test/configCases/hooks/should-emit-2/rspack.config.js b/tests/rspack-test/configCases/hooks/should-emit-2/rspack.config.js index febdbd9bc3a0..42b6a391c5e3 100644 --- a/tests/rspack-test/configCases/hooks/should-emit-2/rspack.config.js +++ b/tests/rspack-test/configCases/hooks/should-emit-2/rspack.config.js @@ -21,6 +21,9 @@ class Plugin { compiler.options.output.path, "./bundle0.js" ); + if (!fs.existsSync(path.dirname(filePath))) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + } fs.writeFileSync(filePath, customBundleFile); return false; });