From 8df434930027e4d9740f8e3c0d79dcbf79388856 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:57:51 +0000 Subject: [PATCH 1/7] fix: give a path watched with other casing the time info of its file on case-insensitive file systems --- lib/DirectoryWatcher.js | 50 +++++++++++++++++++++++++++++- test/Casing.test.js | 61 ++++++++++++++++++++++++++++++++----- types/DirectoryWatcher.d.ts | 7 +++++ 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/lib/DirectoryWatcher.js b/lib/DirectoryWatcher.js index 72f6aab..7d5ea66 100644 --- a/lib/DirectoryWatcher.js +++ b/lib/DirectoryWatcher.js @@ -233,6 +233,10 @@ class DirectoryWatcher extends EventEmitter { this.files = new Map(); /** @type {Map} */ this.filesWithoutCase = new Map(); + // whether names in this directory ignore case, learned the first time a + // watched path differs from a file on disk only by case + /** @type {boolean | undefined} */ + this.caseInsensitive = undefined; /** @type {Map | boolean>} */ this.directories = new Map(); /** @type {Map>} */ @@ -1155,7 +1159,13 @@ class DirectoryWatcher extends EventEmitter { for (const watcher of watchers) { const { path } = watcher; if (!fileTimestamps.has(path)) { - fileTimestamps.set(path, null); + // on a case-insensitive file system a path with other casing + // is the same file and must change with it + const file = this.getFileWithOtherCase(path); + fileTimestamps.set( + path, + (file !== undefined && this.files.get(file)) || null, + ); } } } @@ -1163,6 +1173,44 @@ class DirectoryWatcher extends EventEmitter { return safeTime; } + /** + * @param {string} target a watched path that is not in `files` + * @returns {string | undefined} the file it names on a case-insensitive file system + */ + getFileWithOtherCase(target) { + if (this.caseInsensitive === false) return; + const key = withoutCase(target); + if (!this.filesWithoutCase.has(key)) return; + let file; + for (const item of this.files.keys()) { + if (withoutCase(item) === key) { + file = item; + break; + } + } + if (file === undefined) return; + if (this.caseInsensitive === undefined) { + let fileStats; + try { + fileStats = fs.lstatSync(file); + } catch (_err) { + // changed meanwhile, the next scan updates `files` + return; + } + try { + const targetStats = fs.lstatSync(target); + this.caseInsensitive = + targetStats.ino === fileStats.ino && + targetStats.dev === fileStats.dev; + } catch (_err) { + // a file removed meanwhile says nothing about the file system + if (!fs.existsSync(file)) return; + this.caseInsensitive = false; + } + } + return this.caseInsensitive ? file : undefined; + } + close() { if (this.closed) return; this.closed = true; diff --git a/test/Casing.test.js b/test/Casing.test.js index 5367fb5..e3c0e17 100644 --- a/test/Casing.test.js +++ b/test/Casing.test.js @@ -101,11 +101,10 @@ if (fsIsCaseInsensitive) { expect(changes).toContain(path.join(fixtures, testFileRename)); expect(removals).not.toContain(path.join(fixtures, testFileRename)); - for (const file of files.keys()) { - if (file.endsWith("hello.txt") && files.get(file)) { - return done(new Error("Renamed file was still in timeInfoEntries")); - } - } + // the old casing still names the renamed file + expect(files.get(path.join(fixtures, testFile))).toBe( + files.get(path.join(fixtures, testFileRename)), + ); return done(); }); @@ -120,11 +119,57 @@ if (fsIsCaseInsensitive) { }); }); }); + + it("should give a file watched with the wrong casing the time info of the file (#228)", (done) => { + testHelper.file("a"); + testHelper.tick(1000, () => { + const w = new Watchpack({ + aggregateTimeout: 100, + }); + const wrongCase = path.join(fixtures, "A"); + w.watch({ files: [wrongCase] }); + testHelper.tick(500, () => { + const before = w.getTimeInfoEntries().get(wrongCase); + expect(before).toBeTruthy(); + w.on("aggregated", () => { + const files = w.getTimeInfoEntries(); + w.close(); + const after = files.get(wrongCase); + expect(after).toBe(files.get(path.join(fixtures, "a"))); + expect(after).not.toEqual(before); + done(); + }); + testHelper.file("a"); + }); + }); + }); }); } else { - describe("casing (no tests)", () => { - it("pass", () => { - expect(true).toBe(true); + describe("casing (case-sensitive file system)", () => { + beforeEach((done) => { + testHelper.before(done); + }); + + afterEach((done) => { + testHelper.after(done); + }); + + it("should report a path with other casing as missing on a case-sensitive file system", (done) => { + testHelper.file("a"); + testHelper.tick(() => { + const w = new Watchpack({ + aggregateTimeout: 100, + }); + const wrongCase = path.join(fixtures, "A"); + w.watch({ files: [wrongCase] }); + testHelper.tick(500, () => { + const files = w.getTimeInfoEntries(); + w.close(); + expect(files.get(wrongCase)).toBeNull(); + expect(files.get(path.join(fixtures, "a"))).toBeTruthy(); + done(); + }); + }); }); }); } diff --git a/types/DirectoryWatcher.d.ts b/types/DirectoryWatcher.d.ts index b135c61..13f92c9 100644 --- a/types/DirectoryWatcher.d.ts +++ b/types/DirectoryWatcher.d.ts @@ -46,6 +46,8 @@ declare class DirectoryWatcher extends EventEmitter<{ files: Map; /** @type {Map} */ filesWithoutCase: Map; + /** @type {boolean | undefined} */ + caseInsensitive: boolean | undefined; /** @type {Map | boolean>} */ directories: Map | boolean>; /** @type {Map>} */ @@ -186,6 +188,11 @@ declare class DirectoryWatcher extends EventEmitter<{ fileTimestamps: TimeInfoEntries, directoryTimestamps: TimeInfoEntries, ): number; + /** + * @param {string} target a watched path that is not in `files` + * @returns {string | undefined} the file it names on a case-insensitive file system + */ + getFileWithOtherCase(target: string): string | undefined; close(): void; } declare namespace DirectoryWatcher { From a2a1fb59285d606fbf540119030e8d52538ad7ea Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:58:11 +0000 Subject: [PATCH 2/7] chore: add changeset --- .changeset/case-insensitive-time-info.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/case-insensitive-time-info.md diff --git a/.changeset/case-insensitive-time-info.md b/.changeset/case-insensitive-time-info.md new file mode 100644 index 0000000..409783f --- /dev/null +++ b/.changeset/case-insensitive-time-info.md @@ -0,0 +1,5 @@ +--- +"watchpack": patch +--- + +On case-insensitive file systems, give a file watched with other casing (e.g. imported as `./B` while the file is `b.js`) the time info of the file instead of `null`, so webpack rebuilds it on every change, not only the first (fixes #228). From 2cfae643dc105040ce9d9a3f315732149d002aac Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:59:54 +0000 Subject: [PATCH 3/7] ci: probe #228 with webpack and run webpack's watch tests with this lib (temporary) --- .github/workflows/test.yml | 71 ++++++++++++++++++++++++++++++ tooling/case-probe.js | 88 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 tooling/case-probe.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c8efb7..fcf9cf3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,77 @@ jobs: - run: npm run lint + # TEMPORARY: reproduces #228 with webpack on case-insensitive file systems + case-probe: + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 22.x + cache: "npm" + + - run: npm ci + + - run: npm install --no-save webpack@5 + + - name: Extract the lib of main + run: | + mkdir -p ../main + git archive origin/main lib | tar -x -C ../main + + - name: Probe with the lib of main + run: node tooling/case-probe.js ../main/lib || echo "main failed" + + - name: Probe with this lib + run: node tooling/case-probe.js lib + + # TEMPORARY: webpack's watch tests with this lib + webpack-watch: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: watchpack + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: webpack/webpack + path: webpack + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 22.x + + - run: yarn --frozen-lockfile --ignore-scripts + working-directory: webpack + + - name: Use this lib + run: | + rm -rf webpack/node_modules/watchpack/lib + cp -R watchpack/lib webpack/node_modules/watchpack/lib + + - run: yarn test:base --testMatch "/test/WatchTestCases.longtest.js" "/test/WatchCacheUnaffectedTestCases.longtest.js" "/test/unitCases/{Watch,WatchClose,WatchDetection,WatchSuspend,NodeWatchFileSystem,Compiler,MultiCompiler}.unittest.js" + working-directory: webpack + test: strategy: fail-fast: true diff --git a/tooling/case-probe.js b/tooling/case-probe.js new file mode 100644 index 0000000..5278701 --- /dev/null +++ b/tooling/case-probe.js @@ -0,0 +1,88 @@ +"use strict"; + +/* eslint-disable no-console, n/no-unsupported-features/node-builtins, n/no-process-exit, import/no-unresolved */ + +// Temporary probe for #228: webpack in watch mode, a module imported with other +// casing than the file has. Every edit must reach the output, not only the first. +// Usage: node tooling/case-probe.js [path to watchpack's lib] +const fs = require("fs"); +const Module = require("module"); +const os = require("os"); +const path = require("path"); + +const lib = path.resolve(process.argv[2] || path.join(__dirname, "..", "lib")); +const resolveFilename = Module._resolveFilename; +Module._resolveFilename = function _resolveFilename(request, ...args) { + if (request === "watchpack") return path.join(lib, "index.js"); + return resolveFilename.call(this, request, ...args); +}; + +const webpack = require("webpack"); + +const EDITS = 3; +const root = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "case-")); +const src = path.join(root, "src"); +fs.mkdirSync(src); +fs.writeFileSync( + path.join(src, "index.js"), + 'import value from "./B";\nconsole.log(value);\n', +); +const file = path.join(src, "b.js"); +let version = 0; +const write = () => fs.writeFileSync(file, `export default "v${version}";\n`); +write(); + +const compiler = webpack({ + mode: "development", + devtool: false, + context: root, + entry: "./src/index.js", + output: { path: path.join(root, "dist") }, + infrastructureLogging: { level: "none" }, +}); + +/** @type {(number | null)[]} */ +const seen = []; +let done = false; +/** @type {import("webpack").Watching} */ +let watching; + +/** + * @param {boolean} ok passed + */ +const finish = (ok) => { + if (done) return; + done = true; + watching.close(() => { + console.log( + `${ok ? "PASS" : "FAIL"} ${lib}: outputs had v${seen.join(", v")} (wanted v0 to v${EDITS})`, + ); + fs.rmSync(root, { recursive: true, force: true }); + process.exit(ok ? 0 : 1); + }); +}; + +const timeout = setTimeout(() => finish(false), 30000); + +watching = compiler.watch({ aggregateTimeout: 100 }, (err, stats) => { + if (err || !stats || stats.hasErrors()) { + console.log(err || (stats && stats.toString("errors-only"))); + clearTimeout(timeout); + return finish(false); + } + const match = /"v(\d+)"/.exec( + fs.readFileSync(path.join(root, "dist", "main.js"), "utf8"), + ); + const outputVersion = match ? Number(match[1]) : null; + seen.push(outputVersion); + if (outputVersion !== version) return; + if (version === EDITS) { + clearTimeout(timeout); + return finish(true); + } + // later than the file system accuracy, so every edit is a new mtime + setTimeout(() => { + version++; + write(); + }, 1500); +}); From 4d6be0c30d2b2115d15bd105200be031ff8515b9 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 02:13:46 +0000 Subject: [PATCH 4/7] ci: extract main's lib inside the repository for the probe (temporary) --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fcf9cf3..40eef2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,13 +68,14 @@ jobs: - run: npm install --no-save webpack@5 + # inside the repository, so it resolves the same dependencies - name: Extract the lib of main run: | - mkdir -p ../main - git archive origin/main lib | tar -x -C ../main + mkdir -p main + git archive origin/main lib | tar -x -C main - name: Probe with the lib of main - run: node tooling/case-probe.js ../main/lib || echo "main failed" + run: node tooling/case-probe.js main/lib || echo "main failed" - name: Probe with this lib run: node tooling/case-probe.js lib From e6a841acd181d2193d44c059a06d5346e8680501 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 02:18:10 +0000 Subject: [PATCH 5/7] ci: run webpack's NodeWatchFileSystem test with watchpack 2.6.1 too (temporary) --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40eef2d..3bb39bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,6 +116,16 @@ jobs: - run: yarn test:base --testMatch "/test/WatchTestCases.longtest.js" "/test/WatchCacheUnaffectedTestCases.longtest.js" "/test/unitCases/{Watch,WatchClose,WatchDetection,WatchSuspend,NodeWatchFileSystem,Compiler,MultiCompiler}.unittest.js" working-directory: webpack + # does the NodeWatchFileSystem unit test fail with the published watchpack too? + - name: NodeWatchFileSystem with watchpack 2.6.1 + if: always() + run: | + mkdir published && cd published && npm pack watchpack@2.6.1 && tar -xzf watchpack-2.6.1.tgz + cd .. + rm -rf webpack/node_modules/watchpack/lib + cp -R published/package/lib webpack/node_modules/watchpack/lib + cd webpack && yarn test:base --testMatch "/test/unitCases/NodeWatchFileSystem.unittest.js" || echo "2.6.1 failed too" + test: strategy: fail-fast: true From 36454dfdf891bd07bef9121a96932ea563d09ed6 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 03:00:08 +0000 Subject: [PATCH 6/7] ci: drop the temporary #228 probe and webpack runs --- .github/workflows/test.yml | 82 ----------------------------------- tooling/case-probe.js | 88 -------------------------------------- 2 files changed, 170 deletions(-) delete mode 100644 tooling/case-probe.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3bb39bc..6c8efb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,88 +44,6 @@ jobs: - run: npm run lint - # TEMPORARY: reproduces #228 with webpack on case-insensitive file systems - case-probe: - strategy: - fail-fast: false - matrix: - os: [macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version: 22.x - cache: "npm" - - - run: npm ci - - - run: npm install --no-save webpack@5 - - # inside the repository, so it resolves the same dependencies - - name: Extract the lib of main - run: | - mkdir -p main - git archive origin/main lib | tar -x -C main - - - name: Probe with the lib of main - run: node tooling/case-probe.js main/lib || echo "main failed" - - - name: Probe with this lib - run: node tooling/case-probe.js lib - - # TEMPORARY: webpack's watch tests with this lib - webpack-watch: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - path: watchpack - - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - repository: webpack/webpack - path: webpack - - - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version: 22.x - - - run: yarn --frozen-lockfile --ignore-scripts - working-directory: webpack - - - name: Use this lib - run: | - rm -rf webpack/node_modules/watchpack/lib - cp -R watchpack/lib webpack/node_modules/watchpack/lib - - - run: yarn test:base --testMatch "/test/WatchTestCases.longtest.js" "/test/WatchCacheUnaffectedTestCases.longtest.js" "/test/unitCases/{Watch,WatchClose,WatchDetection,WatchSuspend,NodeWatchFileSystem,Compiler,MultiCompiler}.unittest.js" - working-directory: webpack - - # does the NodeWatchFileSystem unit test fail with the published watchpack too? - - name: NodeWatchFileSystem with watchpack 2.6.1 - if: always() - run: | - mkdir published && cd published && npm pack watchpack@2.6.1 && tar -xzf watchpack-2.6.1.tgz - cd .. - rm -rf webpack/node_modules/watchpack/lib - cp -R published/package/lib webpack/node_modules/watchpack/lib - cd webpack && yarn test:base --testMatch "/test/unitCases/NodeWatchFileSystem.unittest.js" || echo "2.6.1 failed too" - test: strategy: fail-fast: true diff --git a/tooling/case-probe.js b/tooling/case-probe.js deleted file mode 100644 index 5278701..0000000 --- a/tooling/case-probe.js +++ /dev/null @@ -1,88 +0,0 @@ -"use strict"; - -/* eslint-disable no-console, n/no-unsupported-features/node-builtins, n/no-process-exit, import/no-unresolved */ - -// Temporary probe for #228: webpack in watch mode, a module imported with other -// casing than the file has. Every edit must reach the output, not only the first. -// Usage: node tooling/case-probe.js [path to watchpack's lib] -const fs = require("fs"); -const Module = require("module"); -const os = require("os"); -const path = require("path"); - -const lib = path.resolve(process.argv[2] || path.join(__dirname, "..", "lib")); -const resolveFilename = Module._resolveFilename; -Module._resolveFilename = function _resolveFilename(request, ...args) { - if (request === "watchpack") return path.join(lib, "index.js"); - return resolveFilename.call(this, request, ...args); -}; - -const webpack = require("webpack"); - -const EDITS = 3; -const root = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "case-")); -const src = path.join(root, "src"); -fs.mkdirSync(src); -fs.writeFileSync( - path.join(src, "index.js"), - 'import value from "./B";\nconsole.log(value);\n', -); -const file = path.join(src, "b.js"); -let version = 0; -const write = () => fs.writeFileSync(file, `export default "v${version}";\n`); -write(); - -const compiler = webpack({ - mode: "development", - devtool: false, - context: root, - entry: "./src/index.js", - output: { path: path.join(root, "dist") }, - infrastructureLogging: { level: "none" }, -}); - -/** @type {(number | null)[]} */ -const seen = []; -let done = false; -/** @type {import("webpack").Watching} */ -let watching; - -/** - * @param {boolean} ok passed - */ -const finish = (ok) => { - if (done) return; - done = true; - watching.close(() => { - console.log( - `${ok ? "PASS" : "FAIL"} ${lib}: outputs had v${seen.join(", v")} (wanted v0 to v${EDITS})`, - ); - fs.rmSync(root, { recursive: true, force: true }); - process.exit(ok ? 0 : 1); - }); -}; - -const timeout = setTimeout(() => finish(false), 30000); - -watching = compiler.watch({ aggregateTimeout: 100 }, (err, stats) => { - if (err || !stats || stats.hasErrors()) { - console.log(err || (stats && stats.toString("errors-only"))); - clearTimeout(timeout); - return finish(false); - } - const match = /"v(\d+)"/.exec( - fs.readFileSync(path.join(root, "dist", "main.js"), "utf8"), - ); - const outputVersion = match ? Number(match[1]) : null; - seen.push(outputVersion); - if (outputVersion !== version) return; - if (version === EDITS) { - clearTimeout(timeout); - return finish(true); - } - // later than the file system accuracy, so every edit is a new mtime - setTimeout(() => { - version++; - write(); - }, 1500); -}); From 1be34359dba1a1e65584c6959be0f728ddffbeec Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 04:04:03 +0000 Subject: [PATCH 7/7] fix: decide case sensitivity only from ENOENT, not a transient lstat error --- lib/DirectoryWatcher.js | 7 ++++++- test/DirectoryWatcherExtra.test.js | 31 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/DirectoryWatcher.js b/lib/DirectoryWatcher.js index 7d5ea66..bbdb6af 100644 --- a/lib/DirectoryWatcher.js +++ b/lib/DirectoryWatcher.js @@ -1202,7 +1202,12 @@ class DirectoryWatcher extends EventEmitter { this.caseInsensitive = targetStats.ino === fileStats.ino && targetStats.dev === fileStats.dev; - } catch (_err) { + } catch (err) { + // only a missing name proves case matters; EBUSY and the like + // leave it to the next collection + if (/** @type {NodeJS.ErrnoException} */ (err).code !== "ENOENT") { + return; + } // a file removed meanwhile says nothing about the file system if (!fs.existsSync(file)) return; this.caseInsensitive = false; diff --git a/test/DirectoryWatcherExtra.test.js b/test/DirectoryWatcherExtra.test.js index ec42622..160b57e 100644 --- a/test/DirectoryWatcherExtra.test.js +++ b/test/DirectoryWatcherExtra.test.js @@ -479,6 +479,37 @@ describe("DirectoryWatcher extra coverage", () => { dw.close(); }); + it("getFileWithOtherCase does not decide case sensitivity on a transient lstat error", () => { + testHelper.file("a"); + const dw = new DirectoryWatcher( + getWatcherManager(EMPTY_OPTIONS), + fixtures, + EMPTY_OPTIONS, + ); + const file = path.join(fixtures, "a"); + const otherCase = path.join(fixtures, "A"); + dw.setFileTime(file, Date.now(), true, false, "scan (file)"); + + const fs = require("graceful-fs"); + + const { lstatSync } = fs; + const spy = jest + .spyOn(fs, "lstatSync") + .mockImplementation((target, ...args) => { + if (target === otherCase) { + throw Object.assign(new Error("EBUSY"), { code: "EBUSY" }); + } + return lstatSync(target, ...args); + }); + try { + expect(dw.getFileWithOtherCase(otherCase)).toBeUndefined(); + expect(dw.caseInsensitive).toBeUndefined(); + } finally { + spy.mockRestore(); + dw.close(); + } + }); + it("setFileTime detects a case-insensitive duplicate and triggers a rescan", () => { const dw = new DirectoryWatcher( getWatcherManager(EMPTY_OPTIONS),