Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tests/unit/path.test.ts
6449 views
1
/*
2
* path.test.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*
6
*/
7
8
import { unitTest } from "../test.ts";
9
import { assert } from "testing/asserts";
10
import { join, resolve } from "../../src/deno_ral/path.ts";
11
import { isWindows } from "../../src/deno_ral/platform.ts";
12
import {
13
dirAndStem,
14
removeIfEmptyDir,
15
removeIfExists,
16
resolvePathGlobs,
17
} from "../../src/core/path.ts";
18
import { existsSync } from "../../src/deno_ral/fs.ts";
19
import { docs } from "../utils.ts";
20
21
const workingDir = Deno.makeTempDirSync({ prefix: "quarto-test" });
22
const emptyDir = join(workingDir, "empty");
23
24
// deno-lint-ignore require-await
25
unitTest("path - removeIfExists", async () => {
26
Deno.mkdirSync(emptyDir);
27
removeIfExists(emptyDir);
28
assert(!existsSync(emptyDir), "Directory not removed");
29
30
try {
31
removeIfExists(emptyDir);
32
} catch {
33
assert(false, "Removing non-existent directory threw an exception");
34
}
35
});
36
37
// deno-lint-ignore require-await
38
unitTest("path - removeIfEmptyDir", async () => {
39
Deno.mkdirSync(emptyDir);
40
removeIfEmptyDir(emptyDir);
41
assert(!existsSync(emptyDir), "Empty directory was not removed");
42
43
Deno.mkdirSync(emptyDir);
44
Deno.writeTextFileSync(join(emptyDir, "foo.txt"), "Hello World");
45
46
removeIfEmptyDir(emptyDir);
47
assert(existsSync(emptyDir), "Non-empty directory was removed");
48
49
Deno.removeSync(emptyDir, { recursive: true });
50
});
51
52
interface DirStem {
53
path: string;
54
dir: string;
55
stem: string;
56
}
57
// deno-lint-ignore require-await
58
unitTest("path - dirAndStem", async () => {
59
const dirStemTests: DirStem[] = [
60
{
61
path: "foo/bar.txt",
62
dir: "foo",
63
stem: "bar",
64
},
65
{
66
path: "foo/bar",
67
dir: "foo",
68
stem: "bar",
69
},
70
{
71
path: "foo/bar.txt.bar",
72
dir: "foo",
73
stem: "bar.txt",
74
},
75
{
76
path: "foo/bar/test.txt",
77
dir: "foo/bar",
78
stem: "test",
79
},
80
{
81
path: "/foo/bar/test.txt",
82
dir: "/foo/bar",
83
stem: "test",
84
},
85
];
86
dirStemTests.forEach((dirStem) => {
87
const [dir, stem] = dirAndStem(dirStem.path);
88
assert(dir === dirStem.dir, `Invalid directory ${dir} from dirAndStem`);
89
assert(stem === dirStem.stem, `Invalid stem ${stem} from dirAndStem`);
90
});
91
});
92
93
interface GlobTest {
94
name: string;
95
globs: string[];
96
exclude: string[];
97
incLen: number;
98
excLen: number;
99
}
100
const globPath = docs("globs");
101
102
// deno-lint-ignore require-await
103
unitTest("path - resolvePathGlobs", async () => {
104
const globTests: GlobTest[] = [{
105
name: "simple recursive qmd",
106
globs: ["*.qmd"],
107
exclude: [],
108
incLen: 6,
109
excLen: 0,
110
}, {
111
name: "filter out specific ipynb",
112
globs: ["*.ipynb"],
113
exclude: ["sub1/*.ipynb"],
114
incLen: 4,
115
excLen: 0,
116
}, {
117
name: "exclude glob",
118
globs: ["*.ipynb", "!sub1/*.ipynb"],
119
exclude: [],
120
incLen: 5,
121
excLen: 1,
122
}, {
123
name: "deep path",
124
globs: ["sub3/sub3-2/sub3-2-1/sub3-2-1-1/*.*"],
125
exclude: [],
126
incLen: 5,
127
excLen: 0,
128
}, {
129
name: "filter included file",
130
globs: ["sub3/a.qmd"],
131
exclude: ["sub3/a.qmd"],
132
incLen: 0,
133
excLen: 0,
134
}, {
135
name: "exclude included file",
136
globs: ["sub3/a.qmd", "!sub3/a.qmd"],
137
exclude: [],
138
incLen: 1,
139
excLen: 1,
140
}];
141
globTests.forEach((globTest) => {
142
const resolved = resolvePathGlobs(
143
globPath,
144
globTest.globs,
145
globTest.exclude,
146
);
147
assert(
148
resolved.include.length === globTest.incLen,
149
`Invalid include result: ${globTest.name}`,
150
);
151
assert(
152
resolved.exclude.length === globTest.excLen,
153
`Invalid exclude result: ${globTest.name}`,
154
);
155
});
156
});
157
158
// Test for issue #13892: output-dir: ./ should resolve to same path as .
159
// This validates the fix approach using resolve() for path comparison
160
// deno-lint-ignore require-await
161
unitTest("path - output-dir equivalence with resolve()", async () => {
162
const testDir = Deno.makeTempDirSync({ prefix: "quarto-outputdir-test" });
163
164
// All variations of "current directory" should resolve to the same path
165
// Note: ".\" is Windows-only (backslash separator)
166
const variations = [".", "./", "././", "./."];
167
if (isWindows) {
168
variations.push(".\\");
169
}
170
for (const variation of variations) {
171
const resolved = resolve(testDir, variation);
172
const resolvedDir = resolve(testDir);
173
assert(
174
resolved === resolvedDir,
175
`output-dir "${variation}" should resolve to project dir, got ${resolved} vs ${resolvedDir}`,
176
);
177
}
178
179
// Parent traversal back to project dir should also be equivalent
180
// e.g., project in "quarto-proj", output-dir: "../quarto-proj"
181
const dirName = testDir.split(/[/\\]/).pop()!;
182
const parentRef = `../${dirName}`;
183
const resolvedParentRef = resolve(testDir, parentRef);
184
assert(
185
resolvedParentRef === resolve(testDir),
186
`output-dir "${parentRef}" should resolve to project dir, got ${resolvedParentRef} vs ${resolve(testDir)}`,
187
);
188
189
// Actual subdirectories should NOT be equivalent
190
const subdir = "output";
191
const resolvedSubdir = resolve(testDir, subdir);
192
assert(
193
resolvedSubdir !== resolve(testDir),
194
`output-dir "${subdir}" should NOT resolve to project dir`,
195
);
196
197
Deno.removeSync(testDir, { recursive: true });
198
});
199
200