Skip to content

Commit ec0aa37

Browse files
committed
Add tests for pathUtils and fix regression found from new tests
1 parent 9c6bc86 commit ec0aa37

File tree

3 files changed

+92
-31
lines changed

3 files changed

+92
-31
lines changed

src/pathUtils.ts

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,44 +21,27 @@ import * as path from "path";
2121
* @param pathStr - The path to normalize. Can be a Windows or Unix-style path.
2222
* @param stripExtension - If true, removes the file extension (e.g. .prompt, .agent)
2323
* @returns Normalized path string in the format 'path/to/resource'
24-
*
25-
* @example
26-
* normalizePath("path/to/file.prompt")
27-
* // => 'path/to/file.prompt'
28-
*
29-
* @example
30-
* normalizePath("path/to/file.prompt", true)
31-
* // => 'path/to/file'
32-
*
33-
* @example
34-
* normalizePath("\\windows\\style\\path.prompt")
35-
* // => 'windows/style/path.prompt'
36-
*
37-
* @example
38-
* normalizePath("/leading/slash/path/")
39-
* // => 'leading/slash/path'
40-
*
41-
* @example
42-
* normalizePath("multiple//slashes//path")
43-
* // => 'multiple/slashes/path'
4424
*/
4525
export function normalizePath(
4626
pathStr: string,
4727
stripExtension: boolean = false,
4828
): string {
4929
// Convert Windows backslashes to forward slashes
50-
const normalizedSeparators = pathStr.replace(/\\/g, "/");
30+
let normalizedPath = pathStr.replace(/\\/g, "/");
31+
32+
// Use path.posix to handle path normalization (handles consecutive slashes and . /..)
33+
normalizedPath = path.posix.normalize(normalizedPath);
5134

52-
// Use path.posix to handle path normalization (handles consecutive slashes)
53-
// We use posix to ensure forward slashes are used consistently
54-
let normalizedPath = path.posix.normalize(normalizedSeparators);
35+
// Remove leading/trailing slashes
36+
normalizedPath = normalizedPath.replace(/^\/+|\/+$/g, "");
5537

56-
// Strip extension if requested
57-
if (stripExtension) {
58-
const ext = path.posix.extname(normalizedPath);
59-
normalizedPath = normalizedPath.slice(0, -ext.length);
38+
// Strip extension if requested
39+
if (stripExtension && normalizedPath.includes(".")) {
40+
normalizedPath = path.posix.join(
41+
path.posix.dirname(normalizedPath),
42+
path.posix.basename(normalizedPath, path.posix.extname(normalizedPath)),
43+
);
6044
}
6145

62-
// Remove leading/trailing slashes
63-
return normalizedPath.replace(/^\/+|\/+$/g, "");
46+
return normalizedPath;
6447
}

tests/custom/unit/LRUCache.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import LRUCache from "../../../../src/cache/LRUCache";
1+
import LRUCache from "../../../src/cache/LRUCache";
22

33
describe("LRUCache", () => {
44
let cache: LRUCache<string, number>;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { normalizePath } from "../../../src/pathUtils";
2+
3+
describe("normalizePath", () => {
4+
const testCases = [
5+
// Basic cases
6+
{
7+
input: "path/to/file.prompt",
8+
expectedWithExtension: "path/to/file.prompt",
9+
expectedWithoutExtension: "path/to/file",
10+
},
11+
{
12+
input: "path\\to\\file.agent",
13+
expectedWithExtension: "path/to/file.agent",
14+
expectedWithoutExtension: "path/to/file",
15+
},
16+
{
17+
input: "/leading/slashes/file.prompt",
18+
expectedWithExtension: "leading/slashes/file.prompt",
19+
expectedWithoutExtension: "leading/slashes/file",
20+
},
21+
{
22+
input: "trailing/slashes/file.agent/",
23+
expectedWithExtension: "trailing/slashes/file.agent",
24+
expectedWithoutExtension: "trailing/slashes/file",
25+
},
26+
{
27+
input: "multiple//slashes//file.prompt",
28+
expectedWithExtension: "multiple/slashes/file.prompt",
29+
expectedWithoutExtension: "multiple/slashes/file",
30+
},
31+
// Edge cases
32+
{
33+
input: "path/to/file with spaces.prompt",
34+
expectedWithExtension: "path/to/file with spaces.prompt",
35+
expectedWithoutExtension: "path/to/file with spaces",
36+
},
37+
{
38+
input: "path/to/file\\with\\backslashes.prompt",
39+
expectedWithExtension: "path/to/file/with/backslashes.prompt",
40+
expectedWithoutExtension: "path/to/file/with/backslashes",
41+
},
42+
{
43+
input: "path/to/unicode/文件.prompt",
44+
expectedWithExtension: "path/to/unicode/文件.prompt",
45+
expectedWithoutExtension: "path/to/unicode/文件",
46+
},
47+
{
48+
input: "path/to/special/chars/!@#$%^&*().prompt",
49+
expectedWithExtension: "path/to/special/chars/!@#$%^&*().prompt",
50+
expectedWithoutExtension: "path/to/special/chars/!@#$%^&*()",
51+
},
52+
];
53+
54+
test.each(testCases)(
55+
"normalizes path '$input' correctly",
56+
({ input, expectedWithExtension, expectedWithoutExtension }) => {
57+
// Test without stripping extension
58+
const resultWithExtension = normalizePath(input, false);
59+
expect(resultWithExtension).toBe(expectedWithExtension);
60+
61+
// Test with extension stripping
62+
const resultWithoutExtension = normalizePath(input, true);
63+
expect(resultWithoutExtension).toBe(expectedWithoutExtension);
64+
65+
// Add custom failure messages if needed
66+
if (resultWithExtension !== expectedWithExtension) {
67+
throw new Error(
68+
`Failed with stripExtension=false for '${input}'. Expected '${expectedWithExtension}', got '${resultWithExtension}'`,
69+
);
70+
}
71+
if (resultWithoutExtension !== expectedWithoutExtension) {
72+
throw new Error(
73+
`Failed with stripExtension=true for '${input}'. Expected '${expectedWithoutExtension}', got '${resultWithoutExtension}'`,
74+
);
75+
}
76+
},
77+
);
78+
});

0 commit comments

Comments
 (0)