mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
- Unskip `mobile sidebar shares desktop rail behavior` in `tests/visual/overlay-layers.spec.ts`. - Enable visibility check in `tests/visual/moderation.spec.ts` (removed TODO). - Update `daily-design-system-audit.yml` and `agent-pr-review.yml` to use Node.js 22. - Set `fetch-depth: 0` in `ci.yml` build job to ensure `sw-compat` lint check runs. Co-authored-by: PR0M3TH3AN <[email protected]>
270 lines
9.0 KiB
TypeScript
270 lines
9.0 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import type { Page } from "@playwright/test";
|
|
|
|
declare global {
|
|
interface Window {
|
|
__testToast?: {
|
|
show(message: string, options?: Record<string, unknown>): unknown;
|
|
};
|
|
}
|
|
}
|
|
|
|
test.describe("overlay layering tokens", () => {
|
|
test("mobile sidebar shares desktop rail behavior", async ({ page }) => {
|
|
// Increase timeout for this visual test which is prone to flakiness in CI
|
|
test.setTimeout(120_000);
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
|
|
// Pre-suppress the disclaimer modal to avoid race conditions with app hydration
|
|
await page.addInitScript(() => {
|
|
window.localStorage.setItem("hasSeenDisclaimer", "true");
|
|
});
|
|
|
|
await page.goto("/index.html", { waitUntil: "networkidle" });
|
|
|
|
// Wait for initial fade-in to complete so opacity doesn't interfere with visibility checks
|
|
await page.waitForFunction(
|
|
() => !document.getElementById("sidebar")?.classList.contains("fade-in")
|
|
);
|
|
|
|
// At 768px, we still shouldn't see the mobile FAB if we are in "desktop/tablet" mode,
|
|
// or if we are, the test logic below assumes a sidebar collapse toggle exists.
|
|
await expect(page.locator("#mobileMenuBtn")).toHaveCount(0);
|
|
|
|
const collapseToggle = page.locator("#sidebarCollapseToggle");
|
|
await collapseToggle.scrollIntoViewIfNeeded();
|
|
await expect(collapseToggle).toBeVisible();
|
|
|
|
const initialLayout = await page.evaluate(() => {
|
|
const sidebar = document.getElementById("sidebar");
|
|
const app = document.getElementById("app");
|
|
if (!sidebar || !app) {
|
|
throw new Error("Missing sidebar layout nodes");
|
|
}
|
|
|
|
const ensureProbeStyles = () => {
|
|
if (document.getElementById("sidebar-width-probe-style")) {
|
|
return;
|
|
}
|
|
const style = document.createElement("style");
|
|
style.id = "sidebar-width-probe-style";
|
|
style.textContent = `
|
|
.sidebar-width-probe {
|
|
position: absolute;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.sidebar-width-probe--collapsed {
|
|
width: calc(var(--sidebar-width-collapsed) + var(--sidebar-content-gap));
|
|
}
|
|
|
|
.sidebar-width-probe--expanded {
|
|
width: calc(var(--sidebar-width-expanded) + var(--sidebar-content-gap));
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
};
|
|
|
|
ensureProbeStyles();
|
|
|
|
const computeMargin = (widthVar) => {
|
|
const variant =
|
|
widthVar === "--sidebar-width-collapsed"
|
|
? "sidebar-width-probe--collapsed"
|
|
: widthVar === "--sidebar-width-expanded"
|
|
? "sidebar-width-probe--expanded"
|
|
: null;
|
|
if (!variant) {
|
|
throw new Error(`Unsupported sidebar width variable: ${widthVar}`);
|
|
}
|
|
|
|
const probe = document.createElement("div");
|
|
probe.classList.add("sidebar-width-probe", variant);
|
|
document.body.appendChild(probe);
|
|
const width = parseFloat(window.getComputedStyle(probe).width);
|
|
probe.remove();
|
|
return width;
|
|
};
|
|
|
|
const marginLeft = parseFloat(window.getComputedStyle(app).marginLeft);
|
|
const state = sidebar.classList.contains("sidebar-expanded")
|
|
? "expanded"
|
|
: sidebar.classList.contains("sidebar-collapsed")
|
|
? "collapsed"
|
|
: "unknown";
|
|
|
|
return {
|
|
state,
|
|
marginLeft,
|
|
collapsedMargin: computeMargin("--sidebar-width-collapsed"),
|
|
expandedMargin: computeMargin("--sidebar-width-expanded"),
|
|
overlayExists: Boolean(document.getElementById("sidebarOverlay")),
|
|
};
|
|
});
|
|
|
|
expect(initialLayout.overlayExists).toBe(false);
|
|
expect(initialLayout.state).toBe("collapsed");
|
|
expect(Math.abs(initialLayout.marginLeft - initialLayout.collapsedMargin)).toBeLessThan(0.5);
|
|
|
|
// Ensure the toggle is initialized
|
|
await expect(collapseToggle).toHaveAttribute("data-state", /.*/);
|
|
await collapseToggle.scrollIntoViewIfNeeded();
|
|
await collapseToggle.click({ force: true });
|
|
|
|
await page.waitForFunction(() => {
|
|
const sidebar = document.getElementById("sidebar");
|
|
return (
|
|
sidebar?.classList.contains("sidebar-expanded") &&
|
|
!sidebar?.classList.contains("sidebar-collapsed")
|
|
);
|
|
});
|
|
|
|
// Wait for sidebar transition to complete
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Ensure the footer button is scrolled into view (especially for mobile viewports)
|
|
await page.evaluate(() => {
|
|
const sidebar = document.getElementById("sidebar");
|
|
const footerBtn = document.getElementById("footerDropdownButton");
|
|
if (sidebar && footerBtn) {
|
|
footerBtn.scrollIntoView({ block: "center", inline: "center" });
|
|
}
|
|
});
|
|
|
|
// Wait for scroll to settle and layout to stabilize
|
|
await page.waitForTimeout(500);
|
|
|
|
const expandedLayout = await page.evaluate(() => {
|
|
const sidebar = document.getElementById("sidebar");
|
|
const app = document.getElementById("app");
|
|
if (!sidebar || !app) {
|
|
throw new Error("Missing sidebar layout nodes");
|
|
}
|
|
|
|
const ensureProbeStyles = () => {
|
|
if (document.getElementById("sidebar-width-probe-style")) {
|
|
return;
|
|
}
|
|
const style = document.createElement("style");
|
|
style.id = "sidebar-width-probe-style";
|
|
style.textContent = `
|
|
.sidebar-width-probe {
|
|
position: absolute;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.sidebar-width-probe--collapsed {
|
|
width: calc(var(--sidebar-width-collapsed) + var(--sidebar-content-gap));
|
|
}
|
|
|
|
.sidebar-width-probe--expanded {
|
|
width: calc(var(--sidebar-width-expanded) + var(--sidebar-content-gap));
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
};
|
|
|
|
ensureProbeStyles();
|
|
|
|
const computeMargin = (widthVar) => {
|
|
const variant =
|
|
widthVar === "--sidebar-width-collapsed"
|
|
? "sidebar-width-probe--collapsed"
|
|
: widthVar === "--sidebar-width-expanded"
|
|
? "sidebar-width-probe--expanded"
|
|
: null;
|
|
if (!variant) {
|
|
throw new Error(`Unsupported sidebar width variable: ${widthVar}`);
|
|
}
|
|
|
|
const probe = document.createElement("div");
|
|
probe.classList.add("sidebar-width-probe", variant);
|
|
document.body.appendChild(probe);
|
|
const width = parseFloat(window.getComputedStyle(probe).width);
|
|
probe.remove();
|
|
return width;
|
|
};
|
|
|
|
const marginLeft = parseFloat(window.getComputedStyle(app).marginLeft);
|
|
const state = sidebar.classList.contains("sidebar-expanded")
|
|
? "expanded"
|
|
: sidebar.classList.contains("sidebar-collapsed")
|
|
? "collapsed"
|
|
: "unknown";
|
|
|
|
return {
|
|
state,
|
|
marginLeft,
|
|
expectedMargin: computeMargin("--sidebar-width-expanded"),
|
|
};
|
|
});
|
|
|
|
expect(expandedLayout.state).toBe("expanded");
|
|
expect(Math.abs(expandedLayout.marginLeft - expandedLayout.expectedMargin)).toBeLessThan(0.5);
|
|
|
|
const footerButton = page.locator("#footerDropdownButton");
|
|
await footerButton.scrollIntoViewIfNeeded();
|
|
await expect(footerButton).toBeVisible();
|
|
await footerButton.click();
|
|
await expect(footerButton).toHaveAttribute("aria-expanded", "true");
|
|
|
|
const dropupState = await page.evaluate(() => {
|
|
const footerLinks = document.getElementById("footerLinksContainer");
|
|
if (!footerLinks) {
|
|
throw new Error("Missing footer links container");
|
|
}
|
|
return {
|
|
state: footerLinks.getAttribute("data-state"),
|
|
ariaHidden: footerLinks.getAttribute("aria-hidden"),
|
|
};
|
|
});
|
|
|
|
expect(dropupState.state).toBe("expanded");
|
|
expect(dropupState.ariaHidden).toBe("false");
|
|
});
|
|
|
|
test("toast stack honors overlay toast layer", async ({ page }) => {
|
|
await page.goto("/torrent/beacon.html", { waitUntil: "networkidle" });
|
|
|
|
await page.addScriptTag({
|
|
type: "module",
|
|
content: `
|
|
import { createBeaconToast } from "/torrent/ui/toastService.js";
|
|
window.__testToast = createBeaconToast(document);
|
|
`,
|
|
});
|
|
|
|
await page.waitForFunction(() => Boolean(window.__testToast));
|
|
|
|
await page.evaluate(() => {
|
|
window.__testToast?.show("Layer check one");
|
|
window.__testToast?.show("Layer check two");
|
|
});
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
const toastState = await page.evaluate(() => {
|
|
const container = document.getElementById("beacon-toast-container");
|
|
if (!container) {
|
|
throw new Error("Missing toast container");
|
|
}
|
|
|
|
const toastLayer = window
|
|
.getComputedStyle(document.documentElement)
|
|
.getPropertyValue("--z-overlay-toast")
|
|
.trim();
|
|
|
|
return {
|
|
zIndex: window.getComputedStyle(container).zIndex,
|
|
toastLayer,
|
|
childCount: container.querySelectorAll("[data-beacon-motion]").length,
|
|
};
|
|
});
|
|
|
|
expect(toastState.childCount).toBeGreaterThanOrEqual(2);
|
|
expect(toastState.zIndex).toBe(toastState.toastLayer);
|
|
});
|
|
});
|
|
export {};
|