Skip to content

Commit 748141c

Browse files
cdervclaude
andcommitted
Add test for output-dir: ../dirname pattern
Add test case for when output-dir uses parent traversal to reference the project directory (e.g., project in "quarto-proj" with output-dir: "../quarto-proj"). This edge case was raised in PR review. The fix using resolve() correctly handles this pattern since resolve(dir, "../dirname") equals resolve(dir) when dirname is the current directory name. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fac0869 commit 748141c

8 files changed

Lines changed: 60 additions & 13 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
project:
2-
type: default
2+
type: website
33
output-dir: .
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.quarto/
2+
**/*.quarto_ipynb
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
project:
2+
type: website
3+
output-dir: ../output-dir-parent-ref
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This file should NOT be deleted when rendering with output-dir: ../output-dir-parent-ref
2+
This pattern references the project directory via parent traversal.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: "Output Dir Parent Ref Test"
3+
format: html
4+
---
5+
6+
# Test Document
7+
8+
This document tests that `output-dir: ../output-dir-parent-ref` (referencing the project directory via parent traversal) does not delete the project directory.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
project:
2-
type: default
2+
type: website
33
output-dir: ./

tests/smoke/project/project-output-dir-safety.test.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,30 @@ import { existsSync } from "../../../src/deno_ral/fs.ts";
1212
import { testQuartoCmd } from "../../test.ts";
1313
import { fileExists, noErrors } from "../../verify.ts";
1414

15+
// Helper to clean up website output files from a directory
16+
// Used when output-dir points to the project directory itself
17+
async function cleanWebsiteOutput(dir: string) {
18+
const filesToRemove = ["index.html", "test.html", "search.json"];
19+
const dirsToRemove = ["site_libs", ".quarto"];
20+
21+
for (const file of filesToRemove) {
22+
const path = join(dir, file);
23+
if (existsSync(path)) {
24+
await Deno.remove(path);
25+
}
26+
}
27+
for (const subdir of dirsToRemove) {
28+
const path = join(dir, subdir);
29+
if (existsSync(path)) {
30+
await Deno.remove(path, { recursive: true });
31+
}
32+
}
33+
}
34+
1535
// Helper to create output-dir safety tests
1636
function testOutputDirSafety(
1737
name: string,
18-
outputDir: string | null, // null means output is in project dir
38+
outputDir: string | null, // null means output is in project dir (website type)
1939
) {
2040
const testDir = docs(`project/output-dir-${name}`);
2141
const dir = join(Deno.cwd(), testDir);
@@ -37,17 +57,14 @@ function testOutputDirSafety(
3757
if (existsSync(outputPath)) {
3858
await Deno.remove(outputPath, { recursive: true });
3959
}
40-
} else {
41-
// In-place case - just remove the html file
42-
const htmlFile = join(dir, "test.html");
43-
if (existsSync(htmlFile)) {
44-
await Deno.remove(htmlFile);
60+
// Clean up .quarto directory
61+
const quartoDir = join(dir, ".quarto");
62+
if (existsSync(quartoDir)) {
63+
await Deno.remove(quartoDir, { recursive: true });
4564
}
46-
}
47-
// Clean up .quarto directory
48-
const quartoDir = join(dir, ".quarto");
49-
if (existsSync(quartoDir)) {
50-
await Deno.remove(quartoDir, { recursive: true });
65+
} else {
66+
// In-place website case - clean up website output files only
67+
await cleanWebsiteOutput(dir);
5168
}
5269
},
5370
},
@@ -62,3 +79,8 @@ testOutputDirSafety("dot", null);
6279

6380
// Test 3: output-dir: _output (normal subdirectory case)
6481
testOutputDirSafety("subdir", "_output");
82+
83+
// Test 4: output-dir: ../dirname (parent traversal back to project dir)
84+
// This tests the case where output-dir references the project directory
85+
// via parent traversal (e.g., project in "quarto-proj", output-dir: "../quarto-proj")
86+
testOutputDirSafety("parent-ref", null);

tests/unit/path.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ unitTest("path - output-dir equivalence with resolve()", async () => {
176176
);
177177
}
178178

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+
179189
// Actual subdirectories should NOT be equivalent
180190
const subdir = "output";
181191
const resolvedSubdir = resolve(testDir, subdir);

0 commit comments

Comments
 (0)