|
| 1 | +# Manual Preview Tests |
| 2 | + |
| 3 | +Tests for `quarto preview` behavior that require an interactive session with file-save events. These cannot run in automated smoke tests. |
| 4 | + |
| 5 | +## Automation |
| 6 | + |
| 7 | +Use the `/quarto-preview-test` command for the general workflow of starting preview, verifying with browser automation, and checking logs/filesystem. It documents the tools and patterns. |
| 8 | + |
| 9 | +For browser interaction, `/agent-browser` is preferred over Chrome DevTools MCP (more token-efficient). See the command for details. |
| 10 | + |
| 11 | +## Test Matrix: quarto_ipynb Accumulation (#14281) |
| 12 | + |
| 13 | +After every test involving Jupyter execution (Python/Julia cells), verify: |
| 14 | +1. `ls *.quarto_ipynb*` — at most one `{name}.quarto_ipynb` (no `_1`, `_2` variants) |
| 15 | +2. After Ctrl+C exit — no `.quarto_ipynb` files remain (unless `keep-ipynb: true`) |
| 16 | + |
| 17 | +For tests without Jupyter execution (T9, T10, T11), verify no `.quarto_ipynb` files are created at all. |
| 18 | + |
| 19 | +### P1: Critical |
| 20 | + |
| 21 | +#### T1: Single .qmd with Python — re-render accumulation |
| 22 | + |
| 23 | +- **Setup:** `test.qmd` with Python code cell |
| 24 | +- **Steps:** `quarto preview test.qmd`, save 5 times, check files, Ctrl+C |
| 25 | +- **Expected:** At most one `.quarto_ipynb` at any time. Zero after exit. |
| 26 | +- **Catches:** `invalidateForFile()` not deleting transient file before cache eviction |
| 27 | + |
| 28 | +#### T2: Single .qmd with Python — startup duplicate |
| 29 | + |
| 30 | +- **Setup:** Same `test.qmd` |
| 31 | +- **Steps:** `quarto preview test.qmd`, check files immediately after first render (before any saves), Ctrl+C |
| 32 | +- **Expected:** Exactly one `.quarto_ipynb` during render. Zero after exit. |
| 33 | +- **Catches:** `cmd.ts` not passing ProjectContext to `preview()` |
| 34 | + |
| 35 | +#### T3: .qmd in project — project-level preview |
| 36 | + |
| 37 | +- **Setup:** Website project (`_quarto.yml` with `type: website`), `index.qmd` with Python cell |
| 38 | +- **Steps:** `quarto preview` (project dir), save `index.qmd` 3 times, check files, Ctrl+C |
| 39 | +- **Expected:** At most one `index.quarto_ipynb`. Zero after exit. |
| 40 | +- **Catches:** Fix works when `projectContext()` finds a real project |
| 41 | + |
| 42 | +#### T4: .qmd in project — single file preview |
| 43 | + |
| 44 | +- **Setup:** Same project as T3 |
| 45 | +- **Steps:** `quarto preview index.qmd`, save 3 times, check files, Ctrl+C |
| 46 | +- **Expected:** Same as T3. May redirect to project preview (expected behavior). |
| 47 | +- **Catches:** Context passing works for files inside serveable projects |
| 48 | + |
| 49 | +### P2: Important |
| 50 | + |
| 51 | +#### T5: .qmd with Julia code cells |
| 52 | + |
| 53 | +- **Setup:** `julia-test.qmd` with Julia cell |
| 54 | +- **Steps:** Same as T1 |
| 55 | +- **Expected:** Same as T1. Julia uses the same Jupyter engine path. |
| 56 | + |
| 57 | +#### T6: Rapid successive saves |
| 58 | + |
| 59 | +- **Setup:** Same `test.qmd` as T1 |
| 60 | +- **Steps:** Save 5 times within 2-3 seconds (faster than render completes) |
| 61 | +- **Expected:** At most one `.quarto_ipynb`. Debounce/queue coalesces saves. |
| 62 | +- **Catches:** Race condition in invalidation during in-progress render |
| 63 | + |
| 64 | +#### T7: `keep-ipynb: true` |
| 65 | + |
| 66 | +- **Setup:** `test.qmd` with `keep-ipynb: true` in YAML |
| 67 | +- **Steps:** Preview, save 3 times, Ctrl+C, check files |
| 68 | +- **Expected:** `test.quarto_ipynb` persists after exit (not cleaned up). No `_1` variants during preview. |
| 69 | +- **Catches:** `invalidateForFile()` respects the `transient = false` flag set by `cleanupNotebook()` |
| 70 | + |
| 71 | +#### T8: `--to pdf` format |
| 72 | + |
| 73 | +- **Setup:** Same `test.qmd` (requires TinyTeX) |
| 74 | +- **Steps:** `quarto preview test.qmd --to pdf`, save 3 times |
| 75 | +- **Expected:** Same as T1. Transient notebook logic is format-independent. |
| 76 | + |
| 77 | +#### T9: Plain .qmd — no code cells (regression) |
| 78 | + |
| 79 | +- **Setup:** `plain.qmd` with only markdown content |
| 80 | +- **Steps:** Preview, save 3 times, check for `.quarto_ipynb` files |
| 81 | +- **Expected:** No `.quarto_ipynb` files ever created. |
| 82 | +- **Catches:** Fix is a no-op when no Jupyter engine is involved |
| 83 | + |
| 84 | +#### T10: .qmd with R/knitr engine (regression) |
| 85 | + |
| 86 | +- **Setup:** `r-test.qmd` with R code cell and `engine: knitr` |
| 87 | +- **Steps:** Preview, save 3 times, check for `.quarto_ipynb` files |
| 88 | +- **Expected:** No `.quarto_ipynb` files. Knitr doesn't use Jupyter intermediate. |
| 89 | + |
| 90 | +#### T10b: File excluded from project inputs (regression) |
| 91 | + |
| 92 | +- **Setup:** Website project with `_quarto.yml`. Create `_excluded.qmd` with a Python cell (files starting with `_` are excluded from project inputs by default) |
| 93 | +- **Steps:** `quarto preview _excluded.qmd`, save 3 times, check files, Ctrl+C |
| 94 | +- **Expected:** Falls back to single-file preview (not project preview). At most one `.quarto_ipynb`. |
| 95 | +- **Catches:** Context reuse from cmd.ts incorrectly applying project semantics to excluded files |
| 96 | + |
| 97 | +### P3: Nice-to-Have |
| 98 | + |
| 99 | +#### T11: Native .ipynb file |
| 100 | + |
| 101 | +- **Setup:** `notebook.ipynb` (native Jupyter notebook) |
| 102 | +- **Steps:** Preview, save 3 times |
| 103 | +- **Expected:** No transient `.quarto_ipynb` — the `.ipynb` is the source, not transient. |
| 104 | + |
| 105 | +#### T12: File with spaces in name |
| 106 | + |
| 107 | +- **Setup:** `my document.qmd` with Python cell |
| 108 | +- **Steps:** `quarto preview "my document.qmd"`, save 3 times |
| 109 | +- **Expected:** At most one `my document.quarto_ipynb`. Path normalization handles spaces. |
| 110 | + |
| 111 | +#### T13: File in subdirectory |
| 112 | + |
| 113 | +- **Setup:** `subdir/deep/test.qmd` with Python cell |
| 114 | +- **Steps:** Preview from parent dir, save 3 times |
| 115 | +- **Expected:** At most one transient notebook in `subdir/deep/`. |
| 116 | + |
| 117 | +#### T14: Change code cell content |
| 118 | + |
| 119 | +- **Setup:** `test.qmd` with `x = 1; print(x)` |
| 120 | +- **Steps:** Change to `x = 2`, save; change to `x = 3`, save |
| 121 | +- **Expected:** At most one `.quarto_ipynb`. Code changes trigger re-execution but file is cleaned. |
| 122 | + |
| 123 | +#### T15: Change YAML metadata |
| 124 | + |
| 125 | +- **Setup:** `test.qmd` with `title: "Test"` |
| 126 | +- **Steps:** Change title, save; add `theme: cosmo`, save |
| 127 | +- **Expected:** Same as T1. Metadata changes go through the same render/invalidation path. |
| 128 | + |
| 129 | +#### T16: Multiple .qmd files in project |
| 130 | + |
| 131 | +- **Setup:** Website with `index.qmd` (Python), `about.qmd` (Python), `plain.qmd` (no code) |
| 132 | +- **Steps:** Preview project, edit index, navigate to about, edit about |
| 133 | +- **Expected:** At most one `.quarto_ipynb` per Jupyter-using file. No accumulation. |
| 134 | + |
| 135 | +## Test Matrix: Single-file Preview Root URL (#14298) |
| 136 | + |
| 137 | +After every change to preview URL or handler logic, verify that single-file previews serve content at the root URL and print the correct Browse URL. |
| 138 | + |
| 139 | +### P1: Critical |
| 140 | + |
| 141 | +#### T17: Single-file preview — root URL accessible |
| 142 | + |
| 143 | +- **Setup:** `plain.qmd` with only markdown content (no code cells) |
| 144 | +- **Steps:** `quarto preview plain.qmd --port XXXX --no-browser`, then `curl -s -o /dev/null -w "%{http_code}" http://localhost:XXXX/` |
| 145 | +- **Expected:** HTTP 200. Browse URL prints `http://localhost:XXXX/` (no filename appended). |
| 146 | +- **Catches:** `projectHtmlFileRequestHandler` used for single files (defaultFile=`index.html` instead of output filename), or `previewInitialPath` returning filename instead of `""` |
| 147 | + |
| 148 | +#### T18: Single-file preview — named output URL also accessible |
| 149 | + |
| 150 | +- **Setup:** Same `plain.qmd` |
| 151 | +- **Steps:** `quarto preview plain.qmd --port XXXX --no-browser`, then `curl -s -o /dev/null -w "%{http_code}" http://localhost:XXXX/plain.html` |
| 152 | +- **Expected:** HTTP 200. The output filename path also serves the rendered content. |
| 153 | +- **Catches:** Handler regression where only root or only named path works |
| 154 | + |
| 155 | +### P2: Important |
| 156 | + |
| 157 | +#### T19: Project preview — non-index file URL correct |
| 158 | + |
| 159 | +- **Setup:** Website project with `_quarto.yml`, `index.qmd`, and `about.qmd` |
| 160 | +- **Steps:** `quarto preview --port XXXX --no-browser`, navigate to `http://localhost:XXXX/about.html` |
| 161 | +- **Expected:** HTTP 200. Browse URL may include path for non-index files in project context. |
| 162 | +- **Catches:** `isSingleFile` guard accidentally excluding real project files from path computation |
| 163 | + |
| 164 | +## Test File Templates |
| 165 | + |
| 166 | +**Minimal Python .qmd:** |
| 167 | +```yaml |
| 168 | +--- |
| 169 | +title: "Preview Test" |
| 170 | +--- |
| 171 | +``` |
| 172 | + |
| 173 | +```` |
| 174 | +```{python} |
| 175 | +print("Hello from Python") |
| 176 | +``` |
| 177 | +```` |
| 178 | + |
| 179 | +``` |
| 180 | +Edit this line to trigger re-renders. |
| 181 | +``` |
| 182 | + |
| 183 | +**Minimal website project (`_quarto.yml`):** |
| 184 | +```yaml |
| 185 | +project: |
| 186 | + type: website |
| 187 | +``` |
| 188 | +
|
| 189 | +**keep-ipynb variant:** |
| 190 | +```yaml |
| 191 | +--- |
| 192 | +title: "Keep ipynb Test" |
| 193 | +execute: |
| 194 | + keep-ipynb: true |
| 195 | +--- |
| 196 | +``` |
0 commit comments