diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index 57eb1f4d8e7..97b8221e14c 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -6,6 +6,7 @@ All changes included in 1.11: ## Accessibility +- ([#13463](https://github.com/quarto-dev/quarto-cli/issues/13463)): The dark/light mode toggle is now a switch (`button` with `role="switch"`, `aria-checked`, and a localized `aria-label`) instead of a nameless link, in all three places it is created: website navbars/sidebars, plain documents with a light/dark theme pair, and multi-page dashboards. Screen readers now announce the control's name, role, and which mode is active, and Space activates it as well as Enter. Custom CSS targeting `a.quarto-color-scheme-toggle` should now target the `button` element instead. - ([#14615](https://github.com/quarto-dev/quarto-cli/issues/14615)): Fix invalid `role="menu"` on the website navbar's collapse toggle button, flagged by axe-core (`aria-allowed-role`) and WAVE (`aria_menu_broken`) when the navbar collapses to the hamburger at narrow viewports. ## Engines diff --git a/src/format/dashboard/format-dashboard-page.ts b/src/format/dashboard/format-dashboard-page.ts index 6fc36bb9245..9e2336a8405 100644 --- a/src/format/dashboard/format-dashboard-page.ts +++ b/src/format/dashboard/format-dashboard-page.ts @@ -4,6 +4,8 @@ * Copyright (C) 2020-2022 Posit Software, PBC */ +import { kToggleDarkMode } from "../../config/constants.ts"; +import { FormatLanguage } from "../../config/types.ts"; import { Document, Element } from "../../core/deno-dom.ts"; import { recursiveApplyFillClasses } from "./format-dashboard-layout.ts"; import { @@ -25,7 +27,11 @@ interface NavItem { scrolling: boolean; } -export function processPages(doc: Document, dashboardMeta: DashboardMeta) { +export function processPages( + doc: Document, + dashboardMeta: DashboardMeta, + language: FormatLanguage, +) { // Find the pages, if any const pageNodes = doc.querySelectorAll(`.${kPageClass}`); if (pageNodes.length === 0) { @@ -64,10 +70,13 @@ export function processPages(doc: Document, dashboardMeta: DashboardMeta) { // Add a dark mode toggle if needed // If dark and light themes are provided, inject a toggle into the correct spot if (dashboardMeta.hasDarkMode) { - const toggleEl = makeEl("a", { + const toggleEl = makeEl("button", { classes: ["quarto-color-scheme-toggle"], attributes: { - href: "", + type: "button", + role: "switch", + "aria-checked": "false", + "aria-label": language[kToggleDarkMode] || "Toggle dark mode", onclick: "window.quartoToggleColorScheme(); return false;", }, }, doc); diff --git a/src/format/dashboard/format-dashboard.ts b/src/format/dashboard/format-dashboard.ts index 87776f5f39f..f639b6348f0 100644 --- a/src/format/dashboard/format-dashboard.ts +++ b/src/format/dashboard/format-dashboard.ts @@ -23,6 +23,7 @@ import { DependencyHtmlFile, Format, FormatExtras, + FormatLanguage, kDependencies, kHtmlPostprocessors, kSassBundles, @@ -154,7 +155,7 @@ export function dashboardFormat() { extras.html[kHtmlPostprocessors] = extras.html[kHtmlPostprocessors] || []; extras.html[kHtmlPostprocessors].push( - dashboardHtmlPostProcessor(dashboard), + dashboardHtmlPostProcessor(dashboard, format.language), ); extras.metadata = extras.metadata || {}; @@ -317,6 +318,7 @@ registerWriterFormatHandler((format) => { function dashboardHtmlPostProcessor( dashboardMeta: DashboardMeta, + language: FormatLanguage, ) { return (doc: Document): Promise => { const result: HtmlPostProcessResult = { @@ -391,7 +393,7 @@ function dashboardHtmlPostProcessor( processNavigation(doc); // Process pages that may be present in the document - processPages(doc, dashboardMeta); + processPages(doc, dashboardMeta, language); // Process Navbar buttons processNavButtons(doc, dashboardMeta); diff --git a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss index 8c041fd8c71..d346cb20f0e 100644 --- a/src/resources/formats/html/bootstrap/_bootstrap-rules.scss +++ b/src/resources/formats/html/bootstrap/_bootstrap-rules.scss @@ -1680,6 +1680,13 @@ div.callout.callout-style-default > .callout-header { } // dark mode +button.quarto-color-scheme-toggle { + background: transparent; + border: none; + padding: 0; + cursor: pointer; +} + .quarto-reader-toggle .bi::before, .quarto-color-scheme-toggle .bi::before { display: inline-block; 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 6e7adb4d0af..1ca788a3d4b 100644 --- a/src/resources/formats/html/templates/quarto-html-after-body.ejs +++ b/src/resources/formats/html/templates/quarto-html-after-body.ejs @@ -10,17 +10,52 @@ // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { - const a = window.document.createElement('a'); - a.classList.add('top-right'); - a.classList.add('quarto-color-scheme-toggle'); - a.href = ""; - a.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; }; + const toggle = window.document.createElement('button'); + toggle.type = "button"; + toggle.setAttribute("role", "switch"); + toggle.setAttribute("aria-checked", "false"); + toggle.classList.add('top-right'); + toggle.classList.add('quarto-color-scheme-toggle'); + toggle.setAttribute("aria-label", "<%- language['toggle-dark-mode'] %>"); + toggle.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; }; const i = window.document.createElement("i"); i.classList.add('bi'); - a.appendChild(i); - - window.document.body.appendChild(a); + toggle.appendChild(i); + + // Put the toggle in a landmark of its own, so that landmark navigation + // can reach it. + // + // The container is always a fresh direct child of body, and the toggle is + // never moved into page furniture that the theme or the author controls. + // The toggle is positioned against the page, so any ancestor that + // establishes a containing block captures it: a positioned element or a + // grid for position: absolute, and transform, filter, will-change or + // contain for position: fixed. A direct child of body has none of these + // to inherit, and the shift when one appears is silent and visual. + // + // ARIA says a document should have at most one banner, and axe reports a + // duplicate whether or not the banners carry distinct labels. So use a + // header when the page has no banner yet, and a named region otherwise. + // + // A header is a banner wherever it sits, unless it is inside sectioning + // content, so this cannot just look at the children of body: a custom + // page layout wraps the title block header in a plain div. An explicit + // role="banner" always counts. + const sectioning = 'article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]'; + const hasBanner = Array.from( + window.document.querySelectorAll('header:not([role]), [role="banner"]') + ).some(function(el) { + return el.hasAttribute("role") || el.closest(sectioning) === null; + }); + const container = window.document.createElement(hasBanner ? "div" : "header"); + if (hasBanner) { + container.setAttribute("role", "region"); + container.setAttribute("aria-label", "<%- language['toggle-dark-mode'] %>"); + } + container.classList.add('quarto-color-scheme-toggle-container'); + container.appendChild(toggle); + window.document.body.appendChild(container); } setColorSchemeToggle(hasAlternateSentinel()) diff --git a/src/resources/formats/html/templates/quarto-html-before-body.ejs b/src/resources/formats/html/templates/quarto-html-before-body.ejs index a81090e2bfa..ff86c012a94 100644 --- a/src/resources/formats/html/templates/quarto-html-before-body.ejs +++ b/src/resources/formats/html/templates/quarto-html-before-body.ejs @@ -26,6 +26,7 @@ for (let i=0; i < toggles.length; i++) { const toggle = toggles[i]; if (toggle) { + toggle.setAttribute("aria-checked", alternate ? "true" : "false"); if (alternate) { toggle.classList.add("alternate"); } else { diff --git a/src/resources/projects/website/templates/navdarktoggle.ejs b/src/resources/projects/website/templates/navdarktoggle.ejs index 180882d3c9f..fcdf59c00c1 100644 --- a/src/resources/projects/website/templates/navdarktoggle.ejs +++ b/src/resources/projects/website/templates/navdarktoggle.ejs @@ -1 +1 @@ - + diff --git a/tests/docs/playwright/html/color-scheme-toggle-banner.qmd b/tests/docs/playwright/html/color-scheme-toggle-banner.qmd new file mode 100644 index 00000000000..96152d2e305 --- /dev/null +++ b/tests/docs/playwright/html/color-scheme-toggle-banner.qmd @@ -0,0 +1,13 @@ +--- +title: "Fallback color-scheme toggle with an existing banner (#13463)" +title-block-banner: true +format: + html: + theme: + light: flatly + dark: darkly +--- + +`title-block-banner` makes the title block a page level `banner`, so the +floating color-scheme toggle must go into a named region instead of a second +banner. diff --git a/tests/docs/playwright/html/color-scheme-toggle-custom-layout.qmd b/tests/docs/playwright/html/color-scheme-toggle-custom-layout.qmd new file mode 100644 index 00000000000..add4c597643 --- /dev/null +++ b/tests/docs/playwright/html/color-scheme-toggle-custom-layout.qmd @@ -0,0 +1,13 @@ +--- +title: "Fallback color-scheme toggle with a custom page layout (#13463)" +page-layout: custom +format: + html: + theme: + light: flatly + dark: darkly +--- + +`page-layout: custom` wraps the title block header in a plain `div`, which does +not strip its implicit `banner` role. The page therefore already has a banner, +even though the header is not a child of `body`. diff --git a/tests/docs/playwright/html/color-scheme-toggle-plain.qmd b/tests/docs/playwright/html/color-scheme-toggle-plain.qmd new file mode 100644 index 00000000000..6ac7cc9268e --- /dev/null +++ b/tests/docs/playwright/html/color-scheme-toggle-plain.qmd @@ -0,0 +1,11 @@ +--- +title: "Fallback color-scheme toggle without a banner (#13463)" +format: + html: + theme: + light: flatly + dark: darkly +--- + +This document has no page level banner, so the floating color-scheme toggle +gets a `header` of its own to sit in. diff --git a/tests/docs/smoke-all/dark-mode/color-scheme-toggle-dashboard.qmd b/tests/docs/smoke-all/dark-mode/color-scheme-toggle-dashboard.qmd new file mode 100644 index 00000000000..571a91be8f8 --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/color-scheme-toggle-dashboard.qmd @@ -0,0 +1,25 @@ +--- +title: "Dashboard color-scheme toggle is a named switch (#13463)" +format: + dashboard: + theme: + light: flatly + dark: darkly +_quarto: + tests: + dashboard: + ensureHtmlElements: + - + - 'button.quarto-color-scheme-toggle[type="button"][role="switch"][aria-checked="false"][aria-label="Toggle dark mode"]' + - + - 'a.quarto-color-scheme-toggle' +--- + +# Page A + +Multi-page so `processPages()` injects the navbar toggle (single-page +dashboards get the JS-injected fallback toggle instead). + +# Page B + +Second page. diff --git a/tests/docs/smoke-all/dark-mode/color-scheme-toggle-fallback.qmd b/tests/docs/smoke-all/dark-mode/color-scheme-toggle-fallback.qmd new file mode 100644 index 00000000000..108cdf2ded2 --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/color-scheme-toggle-fallback.qmd @@ -0,0 +1,33 @@ +--- +title: "Fallback color-scheme toggle is a named switch (#13463)" +format: + html: + theme: + light: flatly + dark: darkly +_quarto: + tests: + html: + ensureFileRegexMatches: + - + - "createElement\\('button'\\)" + - 'setAttribute\("role", "switch"\)' + - 'setAttribute\("aria-label"' + - 'setAttribute\("aria-checked", alternate' + - 'createElement\(hasBanner \? "div" : "header"\)' + - 'setAttribute\("role", "region"\)' + - + - 'a\.href = ""' + - 'body\.appendChild\(toggle\)' +--- + +The toggle on a plain document (no navbar) is injected by the after-body +script at `DOMContentLoaded`, so these assertions check the script text: the +fallback creates a `button` switch with a name, `setColorSchemeToggle()` keeps +`aria-checked` in sync, the toggle goes into a container rather than straight +onto `body`, and the old nameless-anchor creation is gone. + +Which landmark that container is, and the selector used to find an existing +banner, are deliberately not asserted here: those are structural claims about +the rendered DOM, and `html-color-scheme-toggle.spec.ts` checks them through +the computed role instead. diff --git a/tests/docs/smoke-all/dark-mode/no-toggle-without-dark.qmd b/tests/docs/smoke-all/dark-mode/no-toggle-without-dark.qmd new file mode 100644 index 00000000000..30f59ae6e57 --- /dev/null +++ b/tests/docs/smoke-all/dark-mode/no-toggle-without-dark.qmd @@ -0,0 +1,15 @@ +--- +title: "No color-scheme toggle without a dark theme" +format: + html: + theme: flatly +_quarto: + tests: + html: + ensureFileRegexMatches: + - [] + - + - 'quarto-color-scheme-toggle' +--- + +A light-only document must not emit any toggle markup or toggle script. diff --git a/tests/docs/smoke-all/website/color-scheme-toggle/.gitignore b/tests/docs/smoke-all/website/color-scheme-toggle/.gitignore new file mode 100644 index 00000000000..0b3bf772477 --- /dev/null +++ b/tests/docs/smoke-all/website/color-scheme-toggle/.gitignore @@ -0,0 +1,5 @@ +/.quarto/ +*.html +*.json +site_libs/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/website/color-scheme-toggle/_quarto.yml b/tests/docs/smoke-all/website/color-scheme-toggle/_quarto.yml new file mode 100644 index 00000000000..da83b205935 --- /dev/null +++ b/tests/docs/smoke-all/website/color-scheme-toggle/_quarto.yml @@ -0,0 +1,21 @@ +project: + type: website + output-dir: . + +website: + title: "Website" + reader-mode: true + navbar: + left: + - href: index.qmd + text: Home + tools: + - icon: github + text: Quarto on GitHub + href: https://github.com/quarto-dev/quarto-cli + +format: + html: + theme: + light: flatly + dark: darkly diff --git a/tests/docs/smoke-all/website/color-scheme-toggle/index.qmd b/tests/docs/smoke-all/website/color-scheme-toggle/index.qmd new file mode 100644 index 00000000000..377b071a051 --- /dev/null +++ b/tests/docs/smoke-all/website/color-scheme-toggle/index.qmd @@ -0,0 +1,16 @@ +--- +title: "Website" +_quarto: + tests: + html: + ensureHtmlElements: + - + - 'button.quarto-color-scheme-toggle[type="button"][role="switch"][aria-checked="false"][aria-label="Toggle dark mode"][title="Toggle dark mode"]' + - 'a.quarto-reader-toggle[title]' + - 'a.quarto-navigation-tool[href="https://github.com/quarto-dev/quarto-cli"]' + - + - 'a.quarto-color-scheme-toggle' +--- + +The navbar color-scheme toggle is a named switch (#13463); the reader-mode +toggle and navbar tool links keep their existing shape. diff --git a/tests/integration/playwright/tests/html-color-scheme-toggle.spec.ts b/tests/integration/playwright/tests/html-color-scheme-toggle.spec.ts new file mode 100644 index 00000000000..a90143fbb3f --- /dev/null +++ b/tests/integration/playwright/tests/html-color-scheme-toggle.spec.ts @@ -0,0 +1,81 @@ +import { expect, test } from "@playwright/test"; +import { getUrl } from "../src/utils"; + +// Regression tests for #13463. On a plain document (no navbar to hold it) the +// dark/light mode toggle is created by the after-body script at +// DOMContentLoaded and floated in the top right. It has to land inside a +// landmark, so that landmark navigation can reach it, without changing where +// it is drawn: the toggle is positioned against the page, so its container +// must stay a direct child of body and must not be positioned or a grid. + +test("fallback color-scheme toggle sits in a header landmark", async ({ + page, +}) => { + await page.goto(getUrl("html/color-scheme-toggle-plain.html"), { + waitUntil: "load", + }); + + // No page level banner in this document, so the toggle gets a header of its own. + const container = page.locator( + "body > header.quarto-color-scheme-toggle-container", + ); + await expect(container).toHaveCount(1); + await expect( + container.locator("button.quarto-color-scheme-toggle"), + ).toHaveCount(1); + await expect(page.getByRole("banner")).toHaveCount(1); +}); + +test("fallback color-scheme toggle uses a named region when the page already has a banner", async ({ + page, +}) => { + await page.goto(getUrl("html/color-scheme-toggle-banner.html"), { + waitUntil: "load", + }); + + // title-block-banner already puts a banner at the top level of the document. + await expect(page.locator("body > header#title-block-header")).toHaveCount(1); + + const container = page.locator( + 'body > div.quarto-color-scheme-toggle-container[role="region"]', + ); + await expect(container).toHaveCount(1); + await expect(container).toHaveAttribute("aria-label", /\S/); + await expect( + container.locator("button.quarto-color-scheme-toggle"), + ).toHaveCount(1); + + // The document must not end up with a second banner. Assert this through the + // computed role, not through the selector the implementation uses to probe + // for a banner, otherwise the test cannot catch a fault in that probe. + await expect(page.getByRole("banner")).toHaveCount(1); + + // The container must not become the toggle's containing block: + // #title-block-header is both positioned and a grid, so reparenting the + // toggle into it would move the toggle out of the corner. + const box = await page + .locator("button.quarto-color-scheme-toggle") + .boundingBox(); + const viewport = page.viewportSize(); + expect(box.y).toBeLessThan(60); + expect(viewport.width - (box.x + box.width)).toBeLessThan(60); +}); + +test("fallback color-scheme toggle sees a banner that is not a child of body", async ({ + page, +}) => { + await page.goto(getUrl("html/color-scheme-toggle-custom-layout.html"), { + waitUntil: "load", + }); + + // page-layout: custom puts the title block header inside a div. A div does + // not strip the implicit banner role, so the page already has a banner and + // the toggle must not add a second one. + await expect( + page.locator("body > div > header#title-block-header"), + ).toHaveCount(1); + await expect( + page.locator('body > div.quarto-color-scheme-toggle-container[role="region"]'), + ).toHaveCount(1); + await expect(page.getByRole("banner")).toHaveCount(1); +}); diff --git a/tests/integration/playwright/tests/html-dark-mode-defaultdark.spec.ts b/tests/integration/playwright/tests/html-dark-mode-defaultdark.spec.ts index a21c808674f..bd59886b32d 100644 --- a/tests/integration/playwright/tests/html-dark-mode-defaultdark.spec.ts +++ b/tests/integration/playwright/tests/html-dark-mode-defaultdark.spec.ts @@ -10,13 +10,13 @@ async function check_backgrounds(page, class_, primary, secondary) { const locatr = await page.locator('body').first(); await expect(locatr).toHaveClass(`fullcontent ${class_}`); await expect(locatr).toHaveCSS('background-color', primary); - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); const locatr2 = await page.locator('body').first(); await expect(locatr2).toHaveCSS('background-color', secondary); } async function check_toggle(page, alternate) { - const locatr = await page.locator("a.quarto-color-scheme-toggle"); + const locatr = await page.locator("button.quarto-color-scheme-toggle"); await expect(locatr).toHaveClass(`top-right quarto-color-scheme-toggle${alternate?" alternate":""}`) } diff --git a/tests/integration/playwright/tests/html-dark-mode-defaultlight.spec.ts b/tests/integration/playwright/tests/html-dark-mode-defaultlight.spec.ts index 3fef8fbc605..1a86a589dc7 100644 --- a/tests/integration/playwright/tests/html-dark-mode-defaultlight.spec.ts +++ b/tests/integration/playwright/tests/html-dark-mode-defaultlight.spec.ts @@ -4,14 +4,14 @@ async function check_backgrounds(page, class_, primary, secondary) { const locatr = await page.locator('body').first(); await expect(locatr).toHaveClass(`fullcontent ${class_}`); await expect(locatr).toHaveCSS('background-color', primary); - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); const locatr2 = await page.locator('body').first(); await expect(locatr2).toHaveCSS('background-color', secondary); } async function check_toggle(page, alternate) { - const locatr = await page.locator("a.quarto-color-scheme-toggle"); + const locatr = await page.locator("button.quarto-color-scheme-toggle"); await expect(locatr).toHaveClass(`top-right quarto-color-scheme-toggle${alternate?" alternate":""}`) } diff --git a/tests/integration/playwright/tests/html-dark-mode.spec.ts b/tests/integration/playwright/tests/html-dark-mode.spec.ts index 8df7c3601c2..fd8b6c24888 100644 --- a/tests/integration/playwright/tests/html-dark-mode.spec.ts +++ b/tests/integration/playwright/tests/html-dark-mode.spec.ts @@ -4,7 +4,7 @@ async function check_theme_overrides(page) { const locatr = await page.locator('body').first(); await expect(locatr).toHaveClass('fullcontent quarto-light'); await expect(locatr).toHaveCSS('background-color', 'rgb(252, 252, 252)'); - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); const locatr2 = await page.locator('body').first(); await expect(locatr2).toHaveCSS('background-color', 'rgb(6, 6, 6)'); } @@ -31,7 +31,7 @@ test('Brand false remove project brand', async ({ page }) => { await expect(locatr).toHaveClass('fullcontent quarto-light'); await expect(locatr).toHaveCSS('background-color', 'rgb(255, 255, 255)'); // no toggle - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(0); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(0); }); @@ -50,7 +50,7 @@ test('Syntax highlighting, a11y, with JS', async ({ page }) => { // light highlight stylesheet await expect(importKeyword).toHaveCSS('color', 'rgb(84, 84, 84)'); - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); // dark inline code await expect(pythonCode).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); @@ -75,7 +75,7 @@ test('Syntax highlighting, arrow, with JS', async ({ page }) => { const link = await page.locator('span.al').first(); await expect(link).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); // transparent - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); // dark inline code await expect(pythonCode).toHaveCSS('background-color', 'rgba(37, 41, 46, 0.65)'); await expect(pythonCode).toHaveCSS('color', 'rgb(122, 130, 136)'); diff --git a/tests/integration/playwright/tests/html-themes.spec.ts b/tests/integration/playwright/tests/html-themes.spec.ts index 147dd852b69..2e2bf83ec1d 100644 --- a/tests/integration/playwright/tests/html-themes.spec.ts +++ b/tests/integration/playwright/tests/html-themes.spec.ts @@ -7,7 +7,7 @@ test('Dark and light theme respect user themes', async ({ page }) => { await page.goto('./html/dark-light-theme-custom/'); const locatr = await page.locator('div').filter({ hasText: 'Quarto Playground' }).first() await expect(locatr).toHaveCSS('background-color', 'rgb(255, 0, 0)'); - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); const locatr2 = await page.locator('div').filter({ hasText: 'Quarto Playground' }).first() await expect(locatr2).toHaveCSS('background-color', 'rgb(255, 0, 0)'); }); @@ -17,7 +17,7 @@ test('Dark theming toggle change to dark background ', async ({ page }) => { const locatr = page.getByText('Quarto Playground This is a'); await expect(locatr).toHaveCSS('background-color', 'rgb(255, 255, 255)'); // switching to dark theme using toggle - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); const locatr2 = await page.locator('div').filter({ hasText: 'Quarto Playground' }).first() await expect(locatr2).toHaveCSS('background-color', 'rgb(255, 0, 0)'); }); diff --git a/tests/integration/playwright/tests/html-unified-brand.spec.ts b/tests/integration/playwright/tests/html-unified-brand.spec.ts index 65e8ce4ac87..aff5d87d1d1 100644 --- a/tests/integration/playwright/tests/html-unified-brand.spec.ts +++ b/tests/integration/playwright/tests/html-unified-brand.spec.ts @@ -18,7 +18,7 @@ async function check_link_colors(page, class_) { await expect(linkLocatr).toHaveCSS('background-color', expectedColors[class_]['background-color']); await expect(linkLocatr).toHaveCSS('color', expectedColors[class_].color); - await page.locator("a.quarto-color-scheme-toggle").click(); + await page.locator("button.quarto-color-scheme-toggle").click(); const otherClass_ = otherClass(class_); console.assert(otherClass_ && typeof otherClass_ === 'string'); await expect(linkLocatr).toHaveCSS('background-color', expectedColors[otherClass_!]['background-color']); @@ -27,53 +27,53 @@ async function check_link_colors(page, class_) { test('Light brand in file', async ({ page }) => { await page.goto('./html/unified-brand/light-brand-only-file.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(0); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(0); }); test('Light brand inline', async ({ page }) => { await page.goto('./html/unified-brand/light-brand-only.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(0); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(0); }); test('Light brand with unified entries only light', async ({ page }) => { await page.goto('./html/unified-brand/unified-light-only.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(0); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(0); }); test('Dark brand in file', async ({ page }) => { await page.goto('./html/unified-brand/dark-brand-only-file.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); }); test('Dark brand inline', async ({ page }) => { await page.goto('./html/unified-brand/dark-brand-only.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); }); test('Light and dark brand files', async ({ page }) => { await page.goto('./html/unified-brand/light-dark-brand-file.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-light'); }); test('Light and dark brands inline', async ({ page }) => { await page.goto('./html/unified-brand/light-dark-brand.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-light'); }); test('Dark and light brand files', async ({ page }) => { await page.goto('./html/unified-brand/dark-light-brand-file.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-dark'); }); test('Dark and light brands inline', async ({ page }) => { await page.goto('./html/unified-brand/dark-light-brand.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-dark'); }); @@ -81,21 +81,21 @@ test('Dark and light brands inline', async ({ page }) => { test('Unified light and dark brand file', async ({ page }) => { await page.goto('./html/unified-brand/unified-colors-file.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-light'); }); test('Unified light and dark brand inline', async ({ page }) => { await page.goto('./html/unified-brand/unified-colors.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-light'); }); test('Unified light and dark typography inline', async ({ page }) => { await page.goto('./html/unified-brand/unified-typography.html'); - expect(await page.locator('a.quarto-color-scheme-toggle').count()).toEqual(1); + expect(await page.locator('button.quarto-color-scheme-toggle').count()).toEqual(1); await check_link_colors(page, 'quarto-light'); });