From 773fe09628b70254bade5cbc5111c1bc50450725 Mon Sep 17 00:00:00 2001 From: Danil Vakhrushev <17022974+davakh@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:10:32 +0400 Subject: [PATCH 1/3] feat: add support for win32 paths --- lib/DescriptionFilePlugin.js | 3 +- lib/DescriptionFileUtils.js | 11 +++-- lib/Resolver.js | 3 +- lib/ResolverFactory.js | 6 ++- lib/RestrictionsPlugin.js | 7 +-- lib/RootsPlugin.js | 3 +- lib/SelfReferencePlugin.js | 5 ++- lib/getPaths.js | 14 +++--- lib/util/path.js | 49 +++++++++++++++------ test/path.test.js | 82 +++++++++++++++++++++++++++++++++++- 10 files changed, 147 insertions(+), 36 deletions(-) diff --git a/lib/DescriptionFilePlugin.js b/lib/DescriptionFilePlugin.js index 8bbdb720..043ad9a3 100644 --- a/lib/DescriptionFilePlugin.js +++ b/lib/DescriptionFilePlugin.js @@ -63,8 +63,7 @@ module.exports = class DescriptionFilePlugin { ); return callback(); } - const relativePath = - "." + path.slice(result.directory.length).replace(/\\/g, "/"); + const relativePath = "." + path.slice(result.directory.length); /** @type {ResolveRequest} */ const obj = { ...request, diff --git a/lib/DescriptionFileUtils.js b/lib/DescriptionFileUtils.js index cfa970bc..53bff25e 100644 --- a/lib/DescriptionFileUtils.js +++ b/lib/DescriptionFileUtils.js @@ -5,6 +5,7 @@ "use strict"; +const path = require("path"); const forEachBail = require("./forEachBail"); /** @typedef {import("./Resolver")} Resolver */ @@ -185,12 +186,10 @@ function getField(content, field) { * @returns {string|null} parent directory or null */ function cdUp(directory) { - if (directory === "/") return null; - const i = directory.lastIndexOf("/"), - j = directory.lastIndexOf("\\"); - const p = i < 0 ? j : j < 0 ? i : i < j ? j : i; - if (p < 0) return null; - return directory.slice(0, p || 1); + if (directory === path.sep) return null; + const i = directory.lastIndexOf(path.sep); + if (i < 0) return null; + return directory.slice(0, i || 1); } exports.loadDescriptionFile = loadDescriptionFile; diff --git a/lib/Resolver.js b/lib/Resolver.js index 8806bb1c..46d637c2 100644 --- a/lib/Resolver.js +++ b/lib/Resolver.js @@ -5,6 +5,7 @@ "use strict"; +const nodePath = require("path"); const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable"); const createInnerContext = require("./createInnerContext"); const { parseIdentifier } = require("./util/identifier"); @@ -577,7 +578,7 @@ class Resolver { * @returns {boolean} true, if the path is a directory path */ isDirectory(path) { - return path.endsWith("/"); + return path.endsWith(nodePath.sep); } /** diff --git a/lib/ResolverFactory.js b/lib/ResolverFactory.js index 037567bd..9a30d464 100644 --- a/lib/ResolverFactory.js +++ b/lib/ResolverFactory.js @@ -235,7 +235,11 @@ function createOptions(options) { : ["node_modules"], item => { const type = getType(item); - return type === PathType.Normal || type === PathType.Relative; + return ( + type === PathType.Normal || + type === PathType.RelativePosix || + type === PathType.RelativeWin + ); } ), mainFields, diff --git a/lib/RestrictionsPlugin.js b/lib/RestrictionsPlugin.js index e52ca9d5..7e4ad60f 100644 --- a/lib/RestrictionsPlugin.js +++ b/lib/RestrictionsPlugin.js @@ -5,11 +5,12 @@ "use strict"; +const path = require("path"); + /** @typedef {import("./Resolver")} Resolver */ /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ -const slashCode = "/".charCodeAt(0); -const backslashCode = "\\".charCodeAt(0); +const separatorCode = path.sep.charCodeAt(0); /** * @param {string} path path @@ -20,7 +21,7 @@ const isInside = (path, parent) => { if (!path.startsWith(parent)) return false; if (path.length === parent.length) return true; const charCode = path.charCodeAt(parent.length); - return charCode === slashCode || charCode === backslashCode; + return charCode === separatorCode; }; module.exports = class RestrictionsPlugin { diff --git a/lib/RootsPlugin.js b/lib/RootsPlugin.js index 1d299113..8b0b344a 100644 --- a/lib/RootsPlugin.js +++ b/lib/RootsPlugin.js @@ -5,6 +5,7 @@ "use strict"; +const path = require("path"); const forEachBail = require("./forEachBail"); /** @typedef {import("./Resolver")} Resolver */ @@ -35,7 +36,7 @@ class RootsPlugin { .tapAsync("RootsPlugin", (request, resolveContext, callback) => { const req = request.request; if (!req) return callback(); - if (!req.startsWith("/")) return callback(); + if (!req.startsWith(path.sep)) return callback(); forEachBail( this.roots, diff --git a/lib/SelfReferencePlugin.js b/lib/SelfReferencePlugin.js index a8dc1484..c392bcca 100644 --- a/lib/SelfReferencePlugin.js +++ b/lib/SelfReferencePlugin.js @@ -5,6 +5,7 @@ "use strict"; +const path = require("path"); const DescriptionFileUtils = require("./DescriptionFileUtils"); /** @typedef {import("./Resolver")} Resolver */ @@ -12,7 +13,7 @@ const DescriptionFileUtils = require("./DescriptionFileUtils"); /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */ /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ -const slashCode = "/".charCodeAt(0); +const separatorCode = path.sep.charCodeAt(0); module.exports = class SelfReferencePlugin { /** @@ -56,7 +57,7 @@ module.exports = class SelfReferencePlugin { if ( req.startsWith(name) && (req.length === name.length || - req.charCodeAt(name.length) === slashCode) + req.charCodeAt(name.length) === separatorCode) ) { const remainingRequest = `.${req.slice(name.length)}`; /** @type {ResolveRequest} */ diff --git a/lib/getPaths.js b/lib/getPaths.js index d5835b0d..f6cfe75d 100644 --- a/lib/getPaths.js +++ b/lib/getPaths.js @@ -5,12 +5,14 @@ "use strict"; +const nodePath = require("path"); + /** * @param {string} path path * @returns {{paths: string[], segments: string[]}}} paths and segments */ module.exports = function getPaths(path) { - if (path === "/") return { paths: ["/"], segments: [""] }; + if (path === nodePath.sep) return { paths: [nodePath.sep], segments: [""] }; const parts = path.split(/(.*?[\\/]+)/); const paths = [path]; const segments = [parts[parts.length - 1]]; @@ -19,7 +21,7 @@ module.exports = function getPaths(path) { for (let i = parts.length - 2; i > 2; i -= 2) { paths.push(path); part = parts[i]; - path = path.substring(0, path.length - part.length) || "/"; + path = path.substring(0, path.length - part.length) || nodePath.sep; segments.push(part.slice(0, -1)); } part = parts[1]; @@ -36,10 +38,8 @@ module.exports = function getPaths(path) { * @returns {string|null} basename or null */ module.exports.basename = function basename(path) { - const i = path.lastIndexOf("/"), - j = path.lastIndexOf("\\"); - const p = i < 0 ? j : j < 0 ? i : i < j ? j : i; - if (p < 0) return null; - const s = path.slice(p + 1); + const i = path.lastIndexOf(nodePath.sep); + if (i < 0) return null; + const s = path.slice(i + 1); return s; }; diff --git a/lib/util/path.js b/lib/util/path.js index 4a65c6e3..de18c8da 100644 --- a/lib/util/path.js +++ b/lib/util/path.js @@ -26,7 +26,8 @@ const winNormalize = path.win32.normalize; const PathType = Object.freeze({ Empty: 0, Normal: 1, - Relative: 2, + RelativeWin: 6, + RelativePosix: 2, AbsoluteWin: 3, AbsolutePosix: 4, Internal: 5 @@ -45,7 +46,9 @@ const getType = p => { const c0 = p.charCodeAt(0); switch (c0) { case CHAR_DOT: - return PathType.Relative; + return path.sep.charCodeAt(0) === CHAR_SLASH + ? PathType.RelativePosix + : PathType.RelativeWin; case CHAR_SLASH: return PathType.AbsolutePosix; case CHAR_HASH: @@ -60,8 +63,13 @@ const getType = p => { const c1 = p.charCodeAt(1); switch (c1) { case CHAR_DOT: + return path.sep.charCodeAt(0) === CHAR_SLASH + ? PathType.RelativePosix + : PathType.RelativeWin; case CHAR_SLASH: - return PathType.Relative; + return PathType.RelativePosix; + case CHAR_BACKSLASH: + return PathType.RelativeWin; } return PathType.Normal; } @@ -88,10 +96,15 @@ const getType = p => { const c1 = p.charCodeAt(1); switch (c1) { case CHAR_SLASH: - return PathType.Relative; + return PathType.RelativePosix; + case CHAR_BACKSLASH: + return PathType.RelativeWin; case CHAR_DOT: { const c2 = p.charCodeAt(2); - if (c2 === CHAR_SLASH) return PathType.Relative; + + if (c2 === CHAR_SLASH) return PathType.RelativePosix; + if (c2 === CHAR_BACKSLASH) return PathType.RelativeWin; + return PathType.Normal; } } @@ -127,12 +140,16 @@ const normalize = p => { return p; case PathType.AbsoluteWin: return winNormalize(p); - case PathType.Relative: { + case PathType.RelativePosix: { const r = posixNormalize(p); - return getType(r) === PathType.Relative ? r : `./${r}`; + return getType(r) === PathType.RelativePosix ? r : `./${r}`; + } + case PathType.RelativeWin: { + const r = winNormalize(p); + return getType(r) === PathType.RelativeWin ? r : `.\\${r}`; } } - return posixNormalize(p); + return path.normalize(p); }; exports.normalize = normalize; @@ -152,21 +169,29 @@ const join = (rootPath, request) => { } switch (getType(rootPath)) { case PathType.Normal: - case PathType.Relative: + return path.sep.charCodeAt(0) === CHAR_SLASH + ? posixNormalize(`${rootPath}/${request}`) + : winNormalize(`${rootPath}\\${request}`); + case PathType.RelativePosix: case PathType.AbsolutePosix: return posixNormalize(`${rootPath}/${request}`); + case PathType.RelativeWin: case PathType.AbsoluteWin: return winNormalize(`${rootPath}\\${request}`); } switch (requestType) { case PathType.Empty: return rootPath; - case PathType.Relative: { + case PathType.RelativePosix: { + const r = posixNormalize(rootPath); + return getType(r) === PathType.RelativePosix ? r : `./${r}`; + } + case PathType.RelativeWin: { const r = posixNormalize(rootPath); - return getType(r) === PathType.Relative ? r : `./${r}`; + return getType(r) === PathType.RelativeWin ? r : `.\\${r}`; } } - return posixNormalize(rootPath); + return path.normalize(rootPath); }; exports.join = join; diff --git a/test/path.test.js b/test/path.test.js index 8629ea99..0a57e4b9 100644 --- a/test/path.test.js +++ b/test/path.test.js @@ -1,4 +1,10 @@ -const { checkImportsExportsFieldTarget } = require("../lib/util/path"); +const path = require("path"); +const { + checkImportsExportsFieldTarget, + PathType, + getType, + normalize +} = require("../lib/util/path"); describe("checkImportsExportsFieldTarget", () => { /** @@ -27,3 +33,77 @@ describe("checkImportsExportsFieldTarget", () => { }); }); }); + +describe("getPath", () => { + let pathSepDefault = path.sep; + + afterAll(() => { + path.sep = pathSepDefault; + }); + + ["win32", "posix"].forEach(platform => { + const relativePathType = + platform === "win32" ? "RelativeWin" : "RelativePosix"; + const separator = platform === "win32" ? "\\" : "/"; + + it(`should resolve PathType.${relativePathType} for paths if path.sep is ${platform} (${separator})`, () => { + path.sep = separator; + + expect(getType(".")).toBe(PathType[relativePathType]); + expect(getType("..")).toBe(PathType[relativePathType]); + expect(getType(`..${path.sep}`)).toBe(PathType[relativePathType]); + expect(getType(`..${path.sep}test${path.sep}index.js`)).toBe( + PathType[relativePathType] + ); + }); + }); +}); + +describe("normalize", () => { + let pathSepDefault = path.sep; + + afterEach(() => { + path.sep = pathSepDefault; + }); + + ["win32", "posix"].forEach(platform => { + const separator = platform === "win32" ? "\\" : "/"; + + it(`should correctly normalize for relative/empty paths if path.sep is ${platform} (${separator})`, () => { + path.sep = separator; + + expect(normalize("")).toBe(""); + expect( + normalize( + `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js` + ) + ).toBe(`..${path.sep}hello${path.sep}test.js`); + }); + }); + + it("should correctly normalize for PathType.AbsoluteWin", () => { + path.sep = "\\"; + + expect( + normalize( + `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js` + ) + ).toBe(`..${path.sep}hello${path.sep}test.js`); + }); + + it("should correctly normalize for PathType.AbsolutePosix", () => { + path.sep = "/"; + + expect( + normalize( + `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js` + ) + ).toBe(`..${path.sep}hello${path.sep}test.js`); + }); + + it("should correctly normalize for PathType.Normal", () => { + expect(normalize("enhancedResolve/lib/util/../index")).toBe( + "enhancedResolve/lib/index" + ); + }); +}); From 503ee0ed6f5738daacf291da5bf3f9944144c846 Mon Sep 17 00:00:00 2001 From: Danil Vakhrushev <17022974+davakh@users.noreply.github.com> Date: Wed, 6 Mar 2024 04:27:35 +0400 Subject: [PATCH 2/3] fix: refactor path tests --- test/path.test.js | 97 ++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/test/path.test.js b/test/path.test.js index 0a57e4b9..b4d526db 100644 --- a/test/path.test.js +++ b/test/path.test.js @@ -6,6 +6,11 @@ const { normalize } = require("../lib/util/path"); +// It's enough to use path.sep for tests because the repository has Windows test-runners, +// but for understanding what to expect, we should know about platform and path-type in tests. +const isWin32 = process.platform === "win32"; +const currentPathType = isWin32 ? "win32" : "posix"; + describe("checkImportsExportsFieldTarget", () => { /** * @type {string[]} @@ -35,75 +40,55 @@ describe("checkImportsExportsFieldTarget", () => { }); describe("getPath", () => { - let pathSepDefault = path.sep; - - afterAll(() => { - path.sep = pathSepDefault; - }); - - ["win32", "posix"].forEach(platform => { - const relativePathType = - platform === "win32" ? "RelativeWin" : "RelativePosix"; - const separator = platform === "win32" ? "\\" : "/"; - - it(`should resolve PathType.${relativePathType} for paths if path.sep is ${platform} (${separator})`, () => { - path.sep = separator; - - expect(getType(".")).toBe(PathType[relativePathType]); - expect(getType("..")).toBe(PathType[relativePathType]); - expect(getType(`..${path.sep}`)).toBe(PathType[relativePathType]); - expect(getType(`..${path.sep}test${path.sep}index.js`)).toBe( - PathType[relativePathType] - ); - }); + const relativePathType = isWin32 ? "RelativeWin" : "RelativePosix"; + + it(`should resolve PathType.${relativePathType} for paths if path.sep is ${currentPathType} (${path.sep})`, () => { + expect(getType(".")).toBe(PathType[relativePathType]); + expect(getType("..")).toBe(PathType[relativePathType]); + expect(getType(`..${path.sep}`)).toBe(PathType[relativePathType]); + expect(getType(`..${path.sep}test${path.sep}index.js`)).toBe( + PathType[relativePathType] + ); }); }); describe("normalize", () => { - let pathSepDefault = path.sep; + it(`should correctly normalize for empty path if path.sep is ${currentPathType} (${path.sep})`, () => { + const pathToNormalize = ""; - afterEach(() => { - path.sep = pathSepDefault; + expect(getType(pathToNormalize)).toBe(PathType.Empty); + expect(normalize(pathToNormalize)).toBe(""); }); - ["win32", "posix"].forEach(platform => { - const separator = platform === "win32" ? "\\" : "/"; + it(`should correctly normalize for relative path if path.sep is ${currentPathType} (${path.sep})`, () => { + const pathToNormalize = `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js`; - it(`should correctly normalize for relative/empty paths if path.sep is ${platform} (${separator})`, () => { - path.sep = separator; - - expect(normalize("")).toBe(""); - expect( - normalize( - `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js` - ) - ).toBe(`..${path.sep}hello${path.sep}test.js`); - }); - }); - - it("should correctly normalize for PathType.AbsoluteWin", () => { - path.sep = "\\"; - - expect( - normalize( - `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js` - ) - ).toBe(`..${path.sep}hello${path.sep}test.js`); + expect(getType(pathToNormalize)).toBe( + isWin32 ? PathType.RelativeWin : PathType.RelativePosix + ); + expect(normalize(pathToNormalize)).toBe( + `..${path.sep}hello${path.sep}test.js` + ); }); - it("should correctly normalize for PathType.AbsolutePosix", () => { - path.sep = "/"; + it(`should correctly normalize for absolute path if path.sep is ${currentPathType} (${path.sep})`, () => { + const basePath = `${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js`; + const getAbsolutePathPrefixBasedOnPlatform = pathStr => + isWin32 ? `X:${pathStr}` : pathStr; + const pathToNormalize = getAbsolutePathPrefixBasedOnPlatform(basePath); - expect( - normalize( - `..${path.sep}hello${path.sep}world${path.sep}..${path.sep}test.js` - ) - ).toBe(`..${path.sep}hello${path.sep}test.js`); + expect(getType(pathToNormalize)).toBe( + isWin32 ? PathType.AbsoluteWin : PathType.AbsolutePosix + ); + expect(normalize(pathToNormalize)).toBe( + getAbsolutePathPrefixBasedOnPlatform(`${path.sep}hello${path.sep}test.js`) + ); }); it("should correctly normalize for PathType.Normal", () => { - expect(normalize("enhancedResolve/lib/util/../index")).toBe( - "enhancedResolve/lib/index" - ); + const pathToNormalize = "enhancedResolve/lib/util/../index"; + + expect(getType(pathToNormalize)).toBe(PathType.Normal); + expect(normalize(pathToNormalize)).toBe("enhancedResolve/lib/index"); }); }); From a070b9aba12046662bcb9ff5e9be8a5a6bb1df09 Mon Sep 17 00:00:00 2001 From: Danil Vakhrushev <17022974+davakh@users.noreply.github.com> Date: Wed, 6 Mar 2024 05:00:27 +0400 Subject: [PATCH 3/3] feat: add path-type based tests on main resolve --- test/resolve.test.js | 280 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) diff --git a/test/resolve.test.js b/test/resolve.test.js index d940b877..777437d1 100644 --- a/test/resolve.test.js +++ b/test/resolve.test.js @@ -319,3 +319,283 @@ describe("resolve", () => { }); }); }); + +const isWin32 = process.platform === "win32"; + +describe(`resolve based on path-type: ${isWin32 ? "win32" : "posix"} (${ + path.sep +})`, () => { + // path.join/path.resolve work based on process.platform, + // and we only have to actualize independently passed paths with platform-based separator + // to make sure that it works the same on different platforms + + testResolve( + "absolute path", + fixtures, + path.join(fixtures, "main1.js"), + path.join(fixtures, "main1.js") + ); + + testResolve( + "file with .js", + fixtures, + `.${path.sep}main1.js`, + path.join(fixtures, "main1.js") + ); + testResolve( + "file without extension", + fixtures, + `.${path.sep}main1`, + path.join(fixtures, "main1.js") + ); + testResolve( + "another file with .js", + fixtures, + `.${path.sep}a.js`, + path.join(fixtures, "a.js") + ); + testResolve( + "another file without extension", + fixtures, + `.${path.sep}a`, + path.join(fixtures, "a.js") + ); + testResolve( + "file in module with .js", + fixtures, + `m1${path.sep}a.js`, + path.join(fixtures, "node_modules", "m1", "a.js") + ); + testResolve( + "file in module without extension", + fixtures, + `m1${path.sep}a`, + path.join(fixtures, "node_modules", "m1", "a.js") + ); + testResolve( + "another file in module without extension", + fixtures, + `complexm${path.sep}step1`, + path.join(fixtures, "node_modules", "complexm", "step1.js") + ); + testResolve( + "from submodule to file in sibling module", + path.join(fixtures, "node_modules", "complexm"), + `m2${path.sep}b.js`, + path.join(fixtures, "node_modules", "m2", "b.js") + ); + testResolve( + "from submodule to file in sibling of parent module", + path.join(fixtures, "node_modules", "complexm", "web_modules", "m1"), + `m2${path.sep}b.js`, + path.join(fixtures, "node_modules", "m2", "b.js") + ); + testResolve( + "from nested directory to overwritten file in module", + path.join(fixtures, "multiple_modules"), + `m1${path.sep}a.js`, + path.join(fixtures, "multiple_modules", "node_modules", "m1", "a.js") + ); + testResolve( + "from nested directory to not overwritten file in module", + path.join(fixtures, "multiple_modules"), + `m1${path.sep}b.js`, + path.join(fixtures, "node_modules", "m1", "b.js") + ); + + testResolve( + "file with query", + fixtures, + `.${path.sep}main1.js?query`, + path.join(fixtures, "main1.js") + "?query" + ); + testResolve( + "file with fragment", + fixtures, + `.${path.sep}main1.js#fragment`, + path.join(fixtures, "main1.js") + "#fragment" + ); + testResolve( + "file with fragment and query", + fixtures, + `.${path.sep}main1.js#fragment?query`, + path.join(fixtures, "main1.js") + "#fragment?query" + ); + testResolve( + "file with query and fragment", + fixtures, + `.${path.sep}main1.js?#fragment`, + path.join(fixtures, "main1.js") + "?#fragment" + ); + + testResolve( + "file in module with query", + fixtures, + `m1${path.sep}a?query`, + path.join(fixtures, "node_modules", "m1", "a.js") + "?query" + ); + testResolve( + "file in module with fragment", + fixtures, + `m1${path.sep}a#fragment`, + path.join(fixtures, "node_modules", "m1", "a.js") + "#fragment" + ); + testResolve( + "file in module with fragment and query", + fixtures, + `m1${path.sep}a#fragment?query`, + path.join(fixtures, "node_modules", "m1", "a.js") + "#fragment?query" + ); + testResolve( + "file in module with query and fragment", + fixtures, + `m1${path.sep}a?#fragment`, + path.join(fixtures, "node_modules", "m1", "a.js") + "?#fragment" + ); + + testResolveContext( + "context for fixtures", + fixtures, + `.${path.sep}`, + fixtures + ); + testResolveContext( + "context for fixtures/lib", + fixtures, + `.${path.sep}lib`, + path.join(fixtures, "lib") + ); + testResolveContext( + "context for fixtures with ..", + fixtures, + `.${path.sep}lib${path.sep}..${path.sep}..${path.sep}fixtures${path.sep}.${path.sep}lib${path.sep}..`, + fixtures + ); + + testResolveContext( + "context for fixtures with query", + fixtures, + `.${path.sep}?query`, + fixtures + "?query" + ); + + testResolve( + "differ between directory and file, resolve file", + fixtures, + `.${path.sep}dirOrFile`, + path.join(fixtures, "dirOrFile.js") + ); + testResolve( + "differ between directory and file, resolve directory", + fixtures, + `.${path.sep}dirOrFile${path.sep}`, + path.join(fixtures, "dirOrFile", "index.js") + ); + + testResolve( + "find node_modules outside of node_modules", + path.join(fixtures, "browser-module", "node_modules"), + `m1${path.sep}a`, + path.join(fixtures, "node_modules", "m1", "a.js") + ); + + testResolve( + "don't crash on main field pointing to self", + fixtures, + `.${path.sep}main-field-self`, + path.join(fixtures, "main-field-self", "index.js") + ); + + testResolve( + "don't crash on main field pointing to self", + fixtures, + `.${path.sep}main-field-self2`, + path.join(fixtures, "main-field-self2", "index.js") + ); + + testResolve( + "handle fragment edge case (no fragment)", + fixtures, + `.${path.sep}no#fragment${path.sep}#${path.sep}#`, + path.join(fixtures, "no\0#fragment/\0#", "\0#.js") + ); + + testResolve( + "handle fragment edge case (fragment)", + fixtures, + `.${path.sep}no#fragment${path.sep}#${path.sep}`, + path.join(fixtures, "no.js") + `#fragment${path.sep}#${path.sep}` + ); + + testResolve( + "handle fragment escaping", + fixtures, + `.${path.sep}no\0#fragment${path.sep}\0#${path.sep}\0##fragment`, + path.join(fixtures, "no\0#fragment/\0#", "\0#.js") + "#fragment" + ); + + it("should correctly resolve", function (done) { + const issue238 = path.resolve(fixtures, "issue-238"); + + issue238Resolve( + path.resolve(issue238, "./src/common"), + "config/myObjectFile", + function (err, filename) { + if (err) done(err); + expect(filename).toBeDefined(); + expect(filename).toEqual( + path.resolve(issue238, "./src/common/config/myObjectFile.js") + ); + done(); + } + ); + }); + + it("should correctly resolve with preferRelative", function (done) { + preferRelativeResolve(fixtures, "main1.js", function (err, filename) { + if (err) done(err); + expect(filename).toBeDefined(); + expect(filename).toEqual(path.join(fixtures, "main1.js")); + done(); + }); + }); + + it(`should correctly resolve with preferRelative (separator: ${path.sep})`, function (done) { + preferRelativeResolve( + fixtures, + `m1${path.sep}a.js`, + function (err, filename) { + if (err) done(err); + expect(filename).toBeDefined(); + expect(filename).toEqual( + path.join(fixtures, "node_modules", "m1", "a.js") + ); + done(); + } + ); + }); + + it("should not crash when passing undefined as path", done => { + // @ts-expect-error testing invalid arguments + resolve(fixtures, undefined, err => { + expect(err).toBeInstanceOf(Error); + done(); + }); + }); + + it(`should not crash when passing undefined as context (separator: ${path.sep})`, done => { + // @ts-expect-error testing invalid arguments + resolve({}, undefined, `.${path.sep}test${path.sep}resolve.js`, err => { + expect(err).toBeInstanceOf(Error); + done(); + }); + }); + + it("should not crash when passing undefined everywhere", done => { + // @ts-expect-error testing invalid arguments + resolve(undefined, undefined, undefined, undefined, err => { + expect(err).toBeInstanceOf(Error); + done(); + }); + }); +});