Files
SnapOtter/tests/e2e-docs/search-and-theme.spec.ts

98 lines
4.0 KiB
TypeScript
Raw Permalink Normal View History

import { expect, test } from "@playwright/test";
import { openDocsSearch, waitForHydration } from "./helpers";
test.describe("docs search (Pagefind)", () => {
test("search button is visible", async ({ page }) => {
await page.goto("/");
await expect(page.locator(".blog-search").first()).toBeVisible();
});
test("clicking search opens the dialog with an input", async ({ page }) => {
await page.goto("/");
const input = await openDocsSearch(page);
// It's the real search combobox with a (config-driven) placeholder. Don't
// assert the exact placeholder copy, just that one is present.
await expect(input).toHaveAttribute("role", "combobox");
await expect(input).toHaveAttribute("placeholder", /\S/);
});
test("typing a query shows results", async ({ page }) => {
await page.goto("/");
const input = await openDocsSearch(page);
await input.fill("docker");
await expect(page.locator('[role="option"]').first()).toBeVisible({ timeout: 10_000 });
});
});
test.describe("Theme Toggle", () => {
test("theme toggle exists in DOM", async ({ page }) => {
await page.goto("/guide/getting-started");
const toggle = page.locator(
'button[role="switch"][title*="dark"], button[role="switch"][title*="light"]',
);
await expect(toggle.first()).toBeAttached();
});
test("clicking theme toggle changes appearance", async ({ page }) => {
await page.goto("/guide/getting-started");
// The appearance switch is a Vue handler, so wait for hydration before the
// single click (otherwise the click is swallowed and the class never flips).
await waitForHydration(page);
const html = page.locator("html");
const wasDark = ((await html.getAttribute("class")) ?? "").includes("dark");
// Click whichever theme toggle is actually visible at this viewport: below
// 1440px the nav uses VitePress's native inline toggle, at >=1440px our
// custom cluster toggle takes over. Filtering to :visible skips the hidden
// duplicates that sit earlier in DOM order.
await page.locator('button[role="switch"]:visible').first().click();
// Assert the dark class actually toggled
if (wasDark) {
await expect(html).not.toHaveClass(/dark/);
} else {
await expect(html).toHaveClass(/dark/);
}
});
});
test.describe("GitHub Stars Component", () => {
// The custom nav cluster (toggle + Fund + GitHub Star) only renders at
// >=1440px, where it fits without overflowing the nav. Below that the nav
// defers to VitePress's native responsive layout, so pin a wide desktop here.
test.use({ viewport: { width: 1500, height: 900 } });
test("GitHub star button is visible in navbar", async ({ page }) => {
await page.goto("/");
const githubBtn = page.locator(".github-btn, .github-btn-wrapper").first();
await expect(githubBtn).toBeVisible();
});
test("GitHub star button links to correct repo", async ({ page }) => {
await page.goto("/");
const starLink = page.locator('a[title="Star on GitHub"]').first();
await expect(starLink).toHaveAttribute("href", "https://github.com/snapotter-hq/snapotter");
await expect(starLink).toHaveAttribute("target", "_blank");
});
});
// Regression guard for the nav overflow reported in #556. The custom nav cluster
// used to render inline at every width, pushing the nav past narrow viewports
// (a horizontal scrollbar at 768-959px, off-screen clipping at 1280-1366px).
test.describe("Nav has no horizontal overflow", () => {
for (const width of [768, 834, 900, 1024, 1280, 1366]) {
test(`no horizontal scroll at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await page.goto("/guide/getting-started");
await waitForHydration(page);
const overflow = await page.evaluate(() => {
const de = document.documentElement;
return de.scrollWidth - de.clientWidth;
});
// Allow a 1px sub-pixel rounding margin; the bug produced 300px+ scroll.
expect(overflow).toBeLessThanOrEqual(1);
});
}
});