mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add minimal reproducers for known bugs
- Added reproducer for mobile sidebar margin mismatch (tests/visual/overlay-layers.spec.ts). - Added reproducer for Moderation UI "Show anyway" button visibility (tests/visual/moderation.spec.ts). - Added reproducer for NIP-07 login unit test ReferenceError (tests/nostr-login-permissions.test.mjs). - Added examples/reproducers/playwright.config.ts to support running these tests. - Updated examples/reproducers/README.md with instructions. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
b48320fc9a
commit
1b7575eaab
5 files changed
+561
-16
No files matched your search
@@ -21,3 +21,34 @@ Example structure:
|
||||
- `buildVideoPostEvent` crashes on circular objects if not handled (Fixed).
|
||||
- `normalizeAndAugmentMagnet` crashes on non-iterable `extraTrackers` (Fixed).
|
||||
- `build...` functions crash on `null`/`undefined` input (Fixed).
|
||||
|
||||
# Bug Reproducers
|
||||
|
||||
This directory also contains minimal reproducers for known bugs in the repository.
|
||||
|
||||
## Sidebar Layout (Mobile Margin Mismatch)
|
||||
|
||||
**Issue**: The sidebar layout on mobile viewports has a margin calculation mismatch.
|
||||
**Reproducer**: `examples/reproducers/issue-sidebar-layout/repro.spec.ts`
|
||||
**Run**:
|
||||
```bash
|
||||
npx playwright test -c examples/reproducers/playwright.config.ts examples/reproducers/issue-sidebar-layout/repro.spec.ts
|
||||
```
|
||||
|
||||
## Moderation UI ("Show Anyway" Button Visibility)
|
||||
|
||||
**Issue**: The "Show anyway" button fails to appear in the DOM during tests, causing visibility checks to fail.
|
||||
**Reproducer**: `examples/reproducers/issue-moderation-ui/repro.spec.ts`
|
||||
**Run**:
|
||||
```bash
|
||||
npx playwright test -c examples/reproducers/playwright.config.ts examples/reproducers/issue-moderation-ui/repro.spec.ts
|
||||
```
|
||||
|
||||
## NIP-07 Login Test (ReferenceError)
|
||||
|
||||
**Issue**: The unit test for NIP-07 login fallback fails with `ReferenceError: plainCall is not defined`.
|
||||
**Reproducer**: `examples/reproducers/issue-nip07-login/repro.mjs`
|
||||
**Run**:
|
||||
```bash
|
||||
node examples/reproducers/issue-nip07-login/repro.mjs
|
||||
```
|
||||
@@ -1,43 +1,118 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const FIXTURE_URL = "/docs/moderation/fixtures/index.html";
|
||||
const STORAGE_KEY = "bitvid:moderation:fixture-overrides";
|
||||
const SENTINEL_KEY = "__moderationFixturesInit__";
|
||||
const RESTORE_BUTTON_LABEL = "Restore default moderation";
|
||||
|
||||
async function waitForFixtureReady(page) {
|
||||
await page.waitForSelector('body[data-ready="true"]');
|
||||
}
|
||||
|
||||
test("repro: show anyway override button visibility", async ({ page }) => {
|
||||
// 1. Navigate to fixture
|
||||
function setupStorageReset(page) {
|
||||
return page.addInitScript(({ storageKey, sentinelKey }) => {
|
||||
try {
|
||||
const session = window.sessionStorage;
|
||||
const local = window.localStorage;
|
||||
if (session && !session.getItem(sentinelKey)) {
|
||||
local?.removeItem(storageKey);
|
||||
session.setItem(sentinelKey, "1");
|
||||
}
|
||||
} catch {
|
||||
/* ignore reset failures in tests */
|
||||
}
|
||||
}, { storageKey: STORAGE_KEY, sentinelKey: SENTINEL_KEY });
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupStorageReset(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
try {
|
||||
await page.evaluate(({ key, sentinel }) => {
|
||||
window.localStorage?.removeItem(key);
|
||||
window.sessionStorage?.removeItem(sentinel);
|
||||
}, { key: STORAGE_KEY, sentinel: SENTINEL_KEY });
|
||||
} catch {
|
||||
// ignore cleanup errors when page already closed
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("moderation fixtures", () => {
|
||||
test("show anyway override persists across reloads", async ({ page }) => {
|
||||
await page.goto(FIXTURE_URL, { waitUntil: "networkidle" });
|
||||
await waitForFixtureReady(page);
|
||||
|
||||
// 2. Locate the card that should have the button
|
||||
const overrideCard = page.locator('[data-test-id="show-anyway"]');
|
||||
const thumbnail = overrideCard.locator('img[data-video-thumbnail]');
|
||||
|
||||
// 3. Verify it has the override available attribute
|
||||
await expect(overrideCard).toHaveAttribute(
|
||||
"data-moderation-override-available",
|
||||
"true"
|
||||
);
|
||||
|
||||
// 4. Locate the button
|
||||
const showAnywayButton = overrideCard.getByRole("button", { name: "Show anyway" });
|
||||
const restoreButtonQuery = overrideCard.getByRole("button", {
|
||||
name: RESTORE_BUTTON_LABEL,
|
||||
});
|
||||
|
||||
// 5. Assert visibility (this is expected to fail)
|
||||
console.log("Checking visibility of 'Show anyway' button...");
|
||||
await expect(showAnywayButton).toBeVisible();
|
||||
await expect(overrideCard).toHaveAttribute("data-autoplay-policy", "blocked");
|
||||
await expect(thumbnail).toHaveAttribute("data-thumbnail-state", "blurred");
|
||||
|
||||
console.log("'Show anyway' button is visible. Attempting click...");
|
||||
// 6. Click (this is expected to fail/timeout if not visible)
|
||||
// Uncommented failures:
|
||||
await expect(showAnywayButton).toBeVisible();
|
||||
await expect(restoreButtonQuery).toHaveCount(0);
|
||||
|
||||
// Uncommented failure:
|
||||
await showAnywayButton.click({ force: true });
|
||||
|
||||
// 7. Verify state change
|
||||
/*
|
||||
await expect(overrideCard).toHaveAttribute(
|
||||
"data-moderation-override",
|
||||
"show-anyway"
|
||||
);
|
||||
await expect(overrideCard).not.toHaveAttribute("data-autoplay-policy", "blocked");
|
||||
await expect(thumbnail).not.toHaveAttribute("data-thumbnail-state", "blurred");
|
||||
const restoreButton = overrideCard.getByRole("button", {
|
||||
name: RESTORE_BUTTON_LABEL,
|
||||
});
|
||||
await expect(restoreButton).toBeVisible();
|
||||
await expect(overrideCard.getByRole("button", { name: "Show anyway" })).toHaveCount(0);
|
||||
|
||||
await page.reload({ waitUntil: "networkidle" });
|
||||
await waitForFixtureReady(page);
|
||||
|
||||
const reloadedCard = page.locator('[data-test-id="show-anyway"]');
|
||||
const reloadedThumbnail = reloadedCard.locator('img[data-video-thumbnail]');
|
||||
const reloadedRestoreButton = reloadedCard.getByRole("button", {
|
||||
name: RESTORE_BUTTON_LABEL,
|
||||
});
|
||||
|
||||
await expect(reloadedCard).toHaveAttribute(
|
||||
"data-moderation-override",
|
||||
"show-anyway"
|
||||
);
|
||||
await expect(reloadedCard).not.toHaveAttribute("data-autoplay-policy", "blocked");
|
||||
await expect(reloadedThumbnail).not.toHaveAttribute("data-thumbnail-state", "blurred");
|
||||
await expect(reloadedRestoreButton).toBeVisible();
|
||||
await expect(
|
||||
reloadedCard.getByRole("button", { name: "Show anyway" })
|
||||
).toHaveCount(0);
|
||||
|
||||
await reloadedRestoreButton.click();
|
||||
|
||||
await expect(reloadedCard).not.toHaveAttribute(
|
||||
"data-moderation-override",
|
||||
"show-anyway"
|
||||
);
|
||||
await expect(reloadedCard).toHaveAttribute("data-autoplay-policy", "blocked");
|
||||
await expect(reloadedThumbnail).toHaveAttribute("data-thumbnail-state", "blurred");
|
||||
await expect(
|
||||
reloadedCard.getByRole("button", { name: RESTORE_BUTTON_LABEL })
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
reloadedCard.getByRole("button", { name: "Show anyway" })
|
||||
).toBeVisible();
|
||||
*/
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import "../../../tests/test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { nostrClient } from "../../../js/nostrClientFacade.js";
|
||||
import { NostrClient, __testExports } from "../../../js/nostr/client.js";
|
||||
import nip07Provider from "../../../js/services/authProviders/nip07.js";
|
||||
import { accessControl } from "../../../js/accessControl.js";
|
||||
|
||||
const {
|
||||
DEFAULT_NIP07_ENCRYPTION_METHODS,
|
||||
} = __testExports ?? {};
|
||||
|
||||
const HEX_PUBKEY = "f".repeat(64);
|
||||
const PERMISSIONS_STORAGE_KEY = "bitvid:nip07:permissions";
|
||||
|
||||
function clearStoredPermissions() {
|
||||
if (typeof localStorage === "undefined" || !localStorage) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localStorage.removeItem(PERMISSIONS_STORAGE_KEY);
|
||||
} catch (error) {
|
||||
// Ignore storage cleanup issues in tests
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(() => {
|
||||
clearStoredPermissions();
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
clearStoredPermissions();
|
||||
});
|
||||
|
||||
function setupLoginEnvironment({ enableImpl, getPublicKey = HEX_PUBKEY } = {}) {
|
||||
const previousWindow = typeof global.window === "undefined" ? undefined : global.window;
|
||||
const previousGlobalNostr = global.nostr;
|
||||
const previousGlobalNostrTools = global.NostrTools;
|
||||
const previousCanonicalTools = global.__BITVID_CANONICAL_NOSTR_TOOLS__;
|
||||
const previousNostrToolsReady = global.nostrToolsReady;
|
||||
|
||||
const windowRef = previousWindow ?? global;
|
||||
global.window = windowRef;
|
||||
|
||||
const previousWindowNostr = previousWindow?.nostr;
|
||||
const previousWindowNostrTools = previousWindow?.NostrTools;
|
||||
|
||||
const enableCalls = [];
|
||||
const nostrStub = {
|
||||
enable: (options) => {
|
||||
enableCalls.push(options);
|
||||
if (typeof enableImpl === "function") {
|
||||
return Promise.resolve(enableImpl(options, enableCalls.length));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
getPublicKey: () => Promise.resolve(getPublicKey),
|
||||
};
|
||||
|
||||
windowRef.nostr = nostrStub;
|
||||
global.nostr = nostrStub;
|
||||
|
||||
const toolkitStub = {
|
||||
nip19: {
|
||||
npubEncode: (hex) => `npub${hex}`,
|
||||
},
|
||||
};
|
||||
|
||||
windowRef.NostrTools = toolkitStub;
|
||||
global.NostrTools = toolkitStub;
|
||||
global.__BITVID_CANONICAL_NOSTR_TOOLS__ = toolkitStub;
|
||||
global.nostrToolsReady = Promise.resolve({ ok: true, value: toolkitStub });
|
||||
|
||||
const originalAccessControl = {
|
||||
canAccess: accessControl.canAccess,
|
||||
getWhitelist: accessControl.getWhitelist,
|
||||
getBlacklist: accessControl.getBlacklist,
|
||||
isBlacklisted: accessControl.isBlacklisted,
|
||||
};
|
||||
|
||||
accessControl.canAccess = () => true;
|
||||
accessControl.getWhitelist = () => [];
|
||||
accessControl.getBlacklist = () => [];
|
||||
accessControl.isBlacklisted = () => false;
|
||||
|
||||
if (
|
||||
nostrClient.extensionPermissionCache &&
|
||||
typeof nostrClient.extensionPermissionCache.clear === "function"
|
||||
) {
|
||||
nostrClient.extensionPermissionCache.clear();
|
||||
}
|
||||
clearStoredPermissions();
|
||||
|
||||
return {
|
||||
enableCalls,
|
||||
restore() {
|
||||
accessControl.canAccess = originalAccessControl.canAccess;
|
||||
accessControl.getWhitelist = originalAccessControl.getWhitelist;
|
||||
accessControl.getBlacklist = originalAccessControl.getBlacklist;
|
||||
accessControl.isBlacklisted = originalAccessControl.isBlacklisted;
|
||||
|
||||
if (previousWindow === undefined) {
|
||||
delete global.window;
|
||||
} else {
|
||||
global.window = previousWindow;
|
||||
if (previousWindow) {
|
||||
previousWindow.nostr = previousWindowNostr;
|
||||
previousWindow.NostrTools = previousWindowNostrTools;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousGlobalNostr === undefined) {
|
||||
delete global.nostr;
|
||||
} else {
|
||||
global.nostr = previousGlobalNostr;
|
||||
}
|
||||
|
||||
if (previousGlobalNostrTools === undefined) {
|
||||
delete global.NostrTools;
|
||||
} else {
|
||||
global.NostrTools = previousGlobalNostrTools;
|
||||
}
|
||||
|
||||
global.__BITVID_CANONICAL_NOSTR_TOOLS__ = previousCanonicalTools;
|
||||
|
||||
if (previousNostrToolsReady === undefined) {
|
||||
delete global.nostrToolsReady;
|
||||
} else {
|
||||
global.nostrToolsReady = previousNostrToolsReady;
|
||||
}
|
||||
|
||||
if (
|
||||
nostrClient.extensionPermissionCache &&
|
||||
typeof nostrClient.extensionPermissionCache.clear === "function"
|
||||
) {
|
||||
nostrClient.extensionPermissionCache.clear();
|
||||
}
|
||||
clearStoredPermissions();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("NIP-07 login falls back when structured permissions fail", async () => {
|
||||
clearStoredPermissions();
|
||||
const env = setupLoginEnvironment({
|
||||
enableImpl(options) {
|
||||
if (
|
||||
options &&
|
||||
Array.isArray(options.permissions) &&
|
||||
options.permissions.length &&
|
||||
typeof options.permissions[0] === "object"
|
||||
) {
|
||||
return Promise.reject(new Error("object permissions unsupported"));
|
||||
}
|
||||
if (options && Array.isArray(options.permissions)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("unexpected enable invocation"));
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await nip07Provider.login({ nostrClient });
|
||||
const pubkey = result.pubkey;
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
assert.ok(env.enableCalls.length >= 2, "should retry with alternate payloads");
|
||||
const [objectCall, stringCall] = env.enableCalls;
|
||||
assert.ok(Array.isArray(objectCall?.permissions));
|
||||
assert.equal(
|
||||
typeof objectCall.permissions[0],
|
||||
"object",
|
||||
"structured permission request should be attempted first",
|
||||
);
|
||||
assert.ok(Array.isArray(stringCall?.permissions));
|
||||
assert.equal(
|
||||
typeof stringCall.permissions[0],
|
||||
"string",
|
||||
"string-based permissions should be attempted after structured payloads",
|
||||
);
|
||||
|
||||
// THIS ASSERTION CAUSES ReferenceError: plainCall is not defined
|
||||
assert.equal(
|
||||
plainCall,
|
||||
undefined,
|
||||
"plain enable() should only be attempted if prior payloads fail",
|
||||
);
|
||||
} finally {
|
||||
env.restore();
|
||||
nostrClient.logout();
|
||||
clearStoredPermissions();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,240 @@
|
||||
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", () => {
|
||||
async function dismissDisclaimerModal(page: Page) {
|
||||
const modal = page.locator("#disclaimerModal");
|
||||
if ((await modal.count()) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await page.evaluate(() => {
|
||||
try {
|
||||
window.localStorage?.setItem("hasSeenDisclaimer", "true");
|
||||
} catch (error) {
|
||||
console.warn("Failed to persist disclaimer state", error);
|
||||
}
|
||||
document
|
||||
.querySelectorAll<HTMLElement>("#disclaimerModal")
|
||||
.forEach((node) => {
|
||||
node.classList.add("hidden");
|
||||
node.setAttribute("data-open", "false");
|
||||
});
|
||||
document.documentElement?.classList.remove("modal-open");
|
||||
document.body?.classList.remove("modal-open");
|
||||
});
|
||||
|
||||
await page.waitForFunction(() =>
|
||||
Array.from(
|
||||
document.querySelectorAll("#disclaimerModal")
|
||||
).every((modalElement) => modalElement.classList.contains("hidden"))
|
||||
);
|
||||
}
|
||||
|
||||
test("mobile sidebar shares desktop rail behavior", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/index.html", { waitUntil: "networkidle" });
|
||||
|
||||
await dismissDisclaimerModal(page);
|
||||
|
||||
// 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")
|
||||
);
|
||||
|
||||
await expect(page.locator("#mobileMenuBtn")).toHaveCount(0);
|
||||
|
||||
const collapseToggle = page.locator("#sidebarCollapseToggle");
|
||||
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.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");
|
||||
if (sidebar) {
|
||||
sidebar.scrollTop = sidebar.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
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 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");
|
||||
});
|
||||
});
|
||||
export {};
|
||||
@@ -5,18 +5,24 @@ const PORT = Number.parseInt(process.env.PLAYWRIGHT_PORT ?? "4173", 10);
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL ?? `http://${HOST}:${PORT}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: ".",
|
||||
testDir: "../../",
|
||||
outputDir: "../../artifacts/test-results-repro",
|
||||
timeout: 60_000,
|
||||
reporter: [["list"], ["html", { open: "never" }]],
|
||||
reporter: [["list"]],
|
||||
use: {
|
||||
baseURL: BASE_URL,
|
||||
viewport: { width: 1280, height: 720 },
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "repro",
|
||||
testMatch: "examples/reproducers/**/*.spec.ts",
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
// Config is in examples/reproducers, so cwd is there. We need to serve the repo root.
|
||||
command: `npx http-server ../.. -p ${PORT} -a ${HOST} -c-1 --silent`,
|
||||
command: `npx http-server . -p ${PORT} -a ${HOST} -c-1 --silent`,
|
||||
cwd: "../../",
|
||||
url: `${BASE_URL}/docs/kitchen-sink.html`,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
|
||||
Reference in new issue
Block a user