diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index eae5df492c..1872f1df15 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -2,6 +2,7 @@ All changes included in 1.10: ## Regression fixes +- ([#13583](https://github.com/quarto-dev/quarto-cli/issues/13583)): Fix "Show All Code" and "Hide All Code" in the `code-tools` menu doing nothing when `code-copy` is enabled. (author: @mcanouil) - ([#14267](https://github.com/quarto-dev/quarto-cli/issues/14267)): Fix Windows paths with accented characters (e.g., `C:\Users\Sébastien\`) breaking dart-sass compilation. - ([#14281](https://github.com/quarto-dev/quarto-cli/issues/14281)): Fix transient `.quarto_ipynb` files accumulating during `quarto preview` with Jupyter engine. - ([#14298](https://github.com/quarto-dev/quarto-cli/issues/14298)): Fix `quarto preview` browse URL including output filename (e.g., `hello.html`) for single-file documents, breaking Posit Workbench proxied server access. diff --git a/src/command/render/codetools.ts b/src/command/render/codetools.ts index d7a7cee1d5..a617fd3828 100644 --- a/src/command/render/codetools.ts +++ b/src/command/render/codetools.ts @@ -353,7 +353,7 @@ function resolveCodeTools(format: Format, doc: Document): CodeTools { // if we have requested toggle, make sure there are things to toggle if (codeToolsResolved.toggle) { - const codeDetails = doc.querySelector(".cell > details > .sourceCode"); + const codeDetails = doc.querySelector(".cell details.code-fold"); // we don't OJS hidden cells in this check, since when echo: false, we emit them hidden const codeHidden = doc.querySelector( diff --git a/src/resources/formats/html/templates/quarto-html-after-body.ejs b/src/resources/formats/html/templates/quarto-html-after-body.ejs index 55fade9537..6e7adb4d0a 100644 --- a/src/resources/formats/html/templates/quarto-html-after-body.ejs +++ b/src/resources/formats/html/templates/quarto-html-after-body.ejs @@ -149,16 +149,16 @@ } function toggleCodeHandler(show) { return function(e) { - const detailsSrc = window.document.querySelectorAll(".cell > details > .sourceCode"); - for (let i=0; i .sourceCode"); + const cellCodeDivs = window.document.querySelectorAll(".cell div.sourceCode.cell-code"); const fromCls = show ? "hidden" : "unhidden"; const toCls = show ? "unhidden" : "hidden"; for (let i=0; i` and reveal/hide the `echo: false` cell's source. + +```{ojs} +x = 5 +``` + +```{ojs} +//| echo: false +y = 10 +``` diff --git a/tests/docs/smoke-all/2026/07/18/issue-13583.qmd b/tests/docs/smoke-all/2026/07/18/issue-13583.qmd new file mode 100644 index 0000000000..b51b719388 --- /dev/null +++ b/tests/docs/smoke-all/2026/07/18/issue-13583.qmd @@ -0,0 +1,33 @@ +--- +format: html +engine: jupyter +code-fold: true +code-tools: true +code-copy: true +keep-hidden: true +_quarto: + tests: + html: + ensureHtmlElements: + - - "details.code-fold" # folded cell survives the code-copy scaffold + - "div.code-copy-outer-scaffold > div.sourceCode" + - "div.sourceCode.cell-code.hidden" # echo: false cell + - [] +--- + +With `code-copy` enabled (the default), each code block is wrapped in a +`div.code-copy-outer-scaffold`, which broke the "Show All Code" / "Hide All +Code" `code-tools` selectors that assumed `.sourceCode` was a direct child of +`
` or `.cell` (issue #13583). This document checks the post-fix DOM: +the folded cell keeps its `details.code-fold`, the scaffold wraps the +`div.sourceCode`, and the `echo: false` cell (kept via `keep-hidden: true`) +still carries `div.sourceCode.cell-code.hidden`. + +```{python} +print("visible, folded code") +``` + +```{python} +#| echo: false +print("hidden code") +``` diff --git a/tests/integration/playwright/tests/html-code-tools-toggle.spec.ts b/tests/integration/playwright/tests/html-code-tools-toggle.spec.ts new file mode 100644 index 0000000000..552ea49f96 --- /dev/null +++ b/tests/integration/playwright/tests/html-code-tools-toggle.spec.ts @@ -0,0 +1,45 @@ +import { expect, test } from "@playwright/test"; +import { getUrl } from "../src/utils"; + +// Regression test for #13583. With code-copy enabled (the default), each code +// block is wrapped in div.code-copy-outer-scaffold, which used to break the +// direct-child selectors the code-tools "Show All Code" / "Hide All Code" +// handler relied on. The toggle must still open/close every folded
+// and swap the hidden<->unhidden class on the echo:false cell's source. +test("code-tools Show/Hide All Code toggles folded and hidden code with code-copy", async ({ + page, +}) => { + await page.goto(getUrl("html/code-tools-toggle.html"), { + waitUntil: "load", + }); + + const details = page.locator(".cell details.code-fold"); + const hiddenCode = page.locator("div.sourceCode.cell-code.hidden"); + const unhiddenCode = page.locator("div.sourceCode.cell-code.unhidden"); + + // Baseline: two folded cells, both collapsed; the echo:false cell's source + // is emitted hidden (keep-hidden). + await expect(details).toHaveCount(2); + await expect(details.nth(0)).toHaveJSProperty("open", false); + await expect(details.nth(1)).toHaveJSProperty("open", false); + await expect(hiddenCode).toHaveCount(1); + await expect(unhiddenCode).toHaveCount(0); + + // Show All Code: every
opens, the hidden source becomes unhidden. + await page.locator("#quarto-code-tools-menu").click(); + await page.locator("#quarto-show-all-code").click(); + + await expect(details.nth(0)).toHaveJSProperty("open", true); + await expect(details.nth(1)).toHaveJSProperty("open", true); + await expect(hiddenCode).toHaveCount(0); + await expect(unhiddenCode).toHaveCount(1); + + // Hide All Code: every
closes, the source goes back to hidden. + await page.locator("#quarto-code-tools-menu").click(); + await page.locator("#quarto-hide-all-code").click(); + + await expect(details.nth(0)).toHaveJSProperty("open", false); + await expect(details.nth(1)).toHaveJSProperty("open", false); + await expect(hiddenCode).toHaveCount(1); + await expect(unhiddenCode).toHaveCount(0); +});