Merge pull request #2162 from PR0M3TH3AN/ai/reproducers-known-bugs-20240522-9928543418568370400

docs(repro): add minimal reproducers for known bugs
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-07 16:23:59 -05:00
commit 4381620024
7 files changed
+506

No files matched your search

@@ -0,0 +1,16 @@
# LoginModalController JSDOM Leak Reproducer
Reproduction for the JSDOM async activity leak in LoginModalController tests.
## Usage
```bash
node examples/reproducers/issue-login-modal-leak/repro.mjs
```
## Expected Failure
The script should fail with an uncaught exception due to async activity after test end:
```
# Error: Test "LoginModalController shows NIP-46 handshake panel" ... generated asynchronous activity after the test ended. This activity created the error "ReferenceError: HTMLElement is not defined" ...
```
@@ -0,0 +1,175 @@
import test, { beforeEach, afterEach } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFile } from 'node:fs/promises';
import { JSDOM } from 'jsdom';
import LoginModalController from '../../../js/ui/loginModalController.js';
const loginModalHtml = await readFile(
new URL('../../../components/login-modal.html', import.meta.url),
'utf8',
);
let dom;
let container;
let controller;
async function waitForAnimationFrame(window, cycles = 1) {
for (let i = 0; i < cycles; i += 1) {
await new Promise((resolve) => window.requestAnimationFrame(() => resolve()));
}
}
function createMatchMediaList(query, initialMatches = false) {
let matches = Boolean(initialMatches);
const listeners = new Set();
const notify = () => {
const event = { matches, media: query, type: 'change' };
listeners.forEach((listener) => {
try {
listener(event);
} catch {}
});
};
return {
get matches() {
return matches;
},
set matches(value) {
matches = Boolean(value);
},
media: query,
addEventListener(type, listener) {
if (type === 'change' && typeof listener === 'function') {
listeners.add(listener);
}
},
removeEventListener(type, listener) {
if (type === 'change' && typeof listener === 'function') {
listeners.delete(listener);
}
},
addListener(listener) {
if (typeof listener === 'function') {
listeners.add(listener);
}
},
removeListener(listener) {
if (typeof listener === 'function') {
listeners.delete(listener);
}
},
dispatchEvent(event) {
if (!event || event.type !== 'change') {
return true;
}
listeners.forEach((listener) => {
try {
listener(event);
} catch {}
});
return true;
},
setMatches(value) {
matches = Boolean(value);
notify();
},
};
}
beforeEach(() => {
dom = new JSDOM(
'<!DOCTYPE html><html><body><div id="modalMount"></div></body></html>',
{
url: 'https://example.com',
pretendToBeVisual: true,
},
);
global.window = dom.window;
global.document = dom.window.document;
global.HTMLElement = dom.window.HTMLElement;
global.HTMLButtonElement = dom.window.HTMLButtonElement;
global.HTMLInputElement = dom.window.HTMLInputElement;
global.HTMLTextAreaElement = dom.window.HTMLTextAreaElement;
global.HTMLFormElement = dom.window.HTMLFormElement;
global.HTMLTemplateElement = dom.window.HTMLTemplateElement;
global.Node = dom.window.Node;
global.Element = dom.window.Element;
global.MutationObserver = dom.window.MutationObserver;
window.requestAnimationFrame = (callback) => callback(0);
global.requestAnimationFrame = window.requestAnimationFrame;
window.__matchMediaMocks = [];
window.matchMedia = (query) => {
const mql = createMatchMediaList(query, false);
window.__matchMediaMocks.push(mql);
return mql;
};
global.matchMedia = window.matchMedia;
container = document.getElementById('modalMount');
container.innerHTML = loginModalHtml;
});
afterEach(() => {
if (controller) {
controller.destroy();
controller = null;
}
delete global.window;
delete global.document;
delete global.HTMLElement;
delete global.HTMLButtonElement;
delete global.HTMLInputElement;
delete global.HTMLTextAreaElement;
delete global.HTMLFormElement;
delete global.HTMLTemplateElement;
delete global.Node;
delete global.Element;
delete global.MutationObserver;
delete global.requestAnimationFrame;
delete global.matchMedia;
if (dom) {
dom.window.close();
dom = null;
}
});
function createController(options = {}) {
const modalElement = container.querySelector('#loginModal');
return new LoginModalController({
modalElement,
providers: options.providers || [],
services: options.services || {},
callbacks: options.callbacks || {},
});
}
test('LoginModalController shows NIP-46 handshake panel', async (t) => {
const providers = [{ id: 'nip46', label: 'Remote Signer', login: async () => {} }];
const authService = { requestLogin: async () => {} };
controller = createController({ providers, services: { authService } });
controller.initialize();
const providerButton = container.querySelector('[data-provider-id="nip46"]');
assert.ok(providerButton, 'nip46 provider button should be present');
// Trigger click on provider
const clickEvent = new dom.window.Event('click', { bubbles: true });
providerButton.dispatchEvent(clickEvent);
// Wait for the nip46 form to appear
await waitForAnimationFrame(window, 5);
const form = container.querySelector('[data-nip46-form]');
assert.ok(form, 'nip46 form should be present');
const handshakePanel = form.querySelector('[data-nip46-handshake-panel]');
assert.ok(handshakePanel, 'handshake panel should be present');
assert.ok(!handshakePanel.classList.contains('hidden'), 'handshake panel should not be hidden');
});
@@ -0,0 +1,21 @@
# VideoCard Block State Reproducer
Reproduction for the bug where VideoCard block action fails to restore trusted mute hide state after override.
## Usage
```bash
node examples/reproducers/issue-moderation-block-state/repro.mjs
```
## Expected Failure
The script should fail with an assertion error:
```
not ok 1 - VideoCard block action restores trusted mute hide state after override
...
error: |-
Expected values to be strictly equal:
false !== true
```
@@ -0,0 +1,198 @@
import test from "node:test";
import assert from "node:assert/strict";
import { JSDOM } from "jsdom";
import "../../../tests/test-helpers/setup-localstorage.mjs";
import { VideoCard } from "../../../js/ui/components/VideoCard.js";
import { clearModerationOverride, setModerationOverride } from "../../../js/state/cache.js";
import {
withMockedNostrTools,
createModerationAppHarness,
} from "../../../tests/helpers/moderation-test-helpers.mjs";
import {
applyModerationContextDatasets,
normalizeVideoModerationContext,
} from "../../../js/ui/moderationUiHelpers.js";
import { userBlocks } from "../../../js/userBlocks.js";
function setupDom(t) {
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", {
url: "https://example.com",
pretendToBeVisual: true,
});
const { window } = dom;
const previous = new Map();
const keys = [
"window",
"document",
"HTMLElement",
"Element",
"Node",
"MouseEvent",
"CustomEvent",
"ResizeObserver",
"requestAnimationFrame",
"cancelAnimationFrame",
];
keys.forEach((key) => {
const hadValue = Object.prototype.hasOwnProperty.call(globalThis, key);
previous.set(key, hadValue ? globalThis[key] : Symbol.for("__undefined__"));
if (key === "requestAnimationFrame" && typeof window[key] !== "function") {
window.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
} else if (key === "cancelAnimationFrame" && typeof window[key] !== "function") {
window.cancelAnimationFrame = (id) => clearTimeout(id);
} else if (key === "ResizeObserver" && typeof window[key] !== "function") {
window.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
}
globalThis[key] = window[key];
});
// Mock navigator safely
const originalNavigator = globalThis.navigator;
Object.defineProperty(globalThis, "navigator", {
value: window.navigator,
writable: true,
configurable: true,
});
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
globalThis.requestAnimationFrame = window.requestAnimationFrame;
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = (id) => clearTimeout(id);
globalThis.cancelAnimationFrame = window.cancelAnimationFrame;
}
if (!window.ResizeObserver) {
class ResizeObserverStub {
observe() {}
unobserve() {}
disconnect() {}
}
window.ResizeObserver = ResizeObserverStub;
globalThis.ResizeObserver = ResizeObserverStub;
}
t.after(() => {
dom.window.close();
keys.forEach((key) => {
const previousValue = previous.get(key);
if (previousValue === Symbol.for("__undefined__")) {
delete globalThis[key];
} else {
globalThis[key] = previousValue;
}
});
// Restore navigator
Object.defineProperty(globalThis, "navigator", {
value: originalNavigator,
writable: true,
configurable: true,
});
});
return { window, document: window.document };
}
test("VideoCard block action restores trusted mute hide state after override", async (t) => {
const { document } = setupDom(t);
withMockedNostrTools(t);
const app = await createModerationAppHarness();
app.getActiveModerationThresholds = () => ({
autoplayBlockThreshold: Number.POSITIVE_INFINITY,
blurThreshold: Number.POSITIVE_INFINITY,
trustedMuteHideThreshold: 1,
trustedSpamHideThreshold: Number.POSITIVE_INFINITY,
});
app.pubkey = "f".repeat(64);
app.isUserLoggedIn = () => true;
app.showStatus = () => {};
app.showError = () => {};
app.onVideosShouldRefresh = async () => {};
const originalEnsureLoaded = userBlocks.ensureLoaded;
const originalAddBlock = userBlocks.addBlock;
const originalIsBlocked = userBlocks.isBlocked;
userBlocks.ensureLoaded = async () => {};
userBlocks.addBlock = async () => ({ ok: true });
userBlocks.isBlocked = () => false;
t.after(() => {
userBlocks.ensureLoaded = originalEnsureLoaded;
userBlocks.addBlock = originalAddBlock;
userBlocks.isBlocked = originalIsBlocked;
});
const videoId = "d".repeat(64);
const video = {
id: videoId,
title: "Muted Hide Clip",
pubkey: "e".repeat(64),
moderation: {
trustedMuted: true,
trustedMuteCount: 1,
trustedMuters: ["f".repeat(64)],
trustedCount: 0,
reportType: "nudity",
},
};
app.videosMap.set(video.id, video);
app.currentVideo = video;
app.decorateVideoModeration(video);
const card = new VideoCard({
document,
video,
formatters: {
formatTimeAgo: () => "moments ago",
},
helpers: {
isMagnetSupported: () => false,
},
});
document.body.appendChild(card.getRoot());
assert.equal(
normalizeVideoModerationContext(card.video?.moderation).activeHidden,
true,
);
assert.equal(card.getRoot().dataset.moderationHidden, "true");
card.onModerationOverride = ({ video: overrideVideo, card: overrideCard }) =>
app.handleModerationOverride({ video: overrideVideo, card: overrideCard });
card.moderationActionButton.click();
await new Promise((resolve) => setTimeout(resolve, 0));
const contextAfterOverride = normalizeVideoModerationContext(
card.video?.moderation,
);
assert.equal(contextAfterOverride.overrideActive, true);
assert.equal(contextAfterOverride.activeHidden, false);
assert.ok(card.moderationBlockButton);
card.onModerationBlock = ({ video: targetVideo, card: targetCard }) =>
app.handleModerationBlock({ video: targetVideo, card: targetCard });
card.moderationBlockButton.click();
await new Promise((resolve) => setTimeout(resolve, 0));
const contextAfterHide = normalizeVideoModerationContext(card.video?.moderation);
// This is where it fails in the original test
assert.equal(contextAfterHide.overrideActive, false, "Override should be cleared after block");
assert.equal(contextAfterHide.activeHidden, true, "Content should be hidden again after block");
assert.equal(card.getRoot().dataset.moderationHidden, "true", "Card should have hidden dataset");
assert.equal(card.moderationBlockButton, null, "Block button should be gone");
});
@@ -0,0 +1,27 @@
# Popover Flip Layout Reproducer
Reproduction for the visual regression layout issue where the popover fails to flip to the top in a restricted viewport.
## Prerequisites
You must build the CSS assets before running the test:
```bash
npm run build:css
```
## Usage
```bash
npx playwright test -c examples/reproducers/issue-popover-flip/playwright.config.ts
```
## Expected Failure
The test should fail asserting that the popover is within the viewport (it fails to flip to top, so it overflows bottom):
```
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 250.5
Received: 311.625
```
@@ -0,0 +1,14 @@
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: ".",
use: {
baseURL: "http://127.0.0.1:4173",
},
webServer: {
command: "npx http-server ../../.. -p 4173 -a 127.0.0.1 -c-1 --silent",
url: "http://127.0.0.1:4173",
reuseExistingServer: true,
timeout: 120_000,
},
});
@@ -0,0 +1,55 @@
import { expect, test } from "@playwright/test";
import {
getPanelMetrics,
getPanelWithTriggerMetrics,
} from "../../../tests/e2e/helpers/popoverUtils";
import {
applyReducedMotion,
ensureTestAssets,
failOnConsoleErrors,
waitForRAF,
} from "../../../tests/e2e/helpers/uiTestUtils";
function assertWithinViewport(metrics: {
rect: { top: number; left: number; right: number; bottom: number };
viewport: { width: number; height: number };
}) {
const tolerance = 0.5;
expect(metrics.rect.left).toBeGreaterThanOrEqual(-tolerance);
expect(metrics.rect.top).toBeGreaterThanOrEqual(-tolerance);
expect(metrics.rect.right).toBeLessThanOrEqual(metrics.viewport.width + tolerance);
expect(metrics.rect.bottom).toBeLessThanOrEqual(metrics.viewport.height + tolerance);
}
test.describe("popover layout scenarios", () => {
test.beforeEach(async ({ page }) => {
await ensureTestAssets(page);
await applyReducedMotion(page);
failOnConsoleErrors(page);
await page.goto("/docs/popover-scenarios.html", { waitUntil: "networkidle" });
});
// flaky: Popover fails to flip to top in restricted viewport (floating-ui issue?)
test("keeps the bottom-right grid menu inside the viewport", async ({ page }) => {
// Force a tall body to ensure there is space for the popover to flip to the top
await page.addStyleTag({ content: "body { min-height: 2000px !important; }" });
// Reduce viewport height to force the menu to flip to the top
await page.setViewportSize({ width: 1280, height: 250 });
const trigger = page.locator('[data-test-trigger="grid-bottom-right"]');
// Scroll to bottom to ensure the element is near the viewport edge
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await trigger.click();
const panel = page.locator('[data-test-panel="grid-bottom-right"]');
await expect(panel).toHaveAttribute("data-popover-state", "open");
const metrics = await getPanelMetrics(panel);
assertWithinViewport(metrics);
expect(metrics.state).toBe("open");
expect(metrics.placement.startsWith("top"), `Expected placement to start with 'top' but got '${metrics.placement}'`).toBeTruthy();
expect(metrics.popoverMaxWidth).toBe(metrics.tokenMaxWidth);
expect(metrics.tokenMaxWidth.length).toBeGreaterThan(0);
});
});