mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
560 lines
18 KiB
JavaScript
560 lines
18 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { JSDOM } from "jsdom";
|
|
|
|
import "../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 "../helpers/moderation-test-helpers.mjs";
|
|
import {
|
|
applyModerationContextDatasets,
|
|
normalizeVideoModerationContext,
|
|
} from "../../js/ui/moderationUiHelpers.js";
|
|
import { buildModerationBadgeText } from "../../js/ui/moderationCopy.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 renders moderation badges and respects viewer override", async (t) => {
|
|
const { document } = setupDom(t);
|
|
withMockedNostrTools(t);
|
|
|
|
const app = await createModerationAppHarness();
|
|
app.moderationDecorator.updateSettings({ trustedSpamHideThreshold: 3 });
|
|
const videoId = "a".repeat(64);
|
|
|
|
const video = {
|
|
id: videoId,
|
|
title: "Blurred Clip",
|
|
pubkey: "d".repeat(64),
|
|
moderation: {
|
|
blockAutoplay: true,
|
|
blurThumbnail: true,
|
|
reportType: "nudity",
|
|
trustedCount: 3,
|
|
summary: {
|
|
eventId: videoId,
|
|
totalTrusted: 3,
|
|
types: { nudity: { trusted: 3, total: 3, latest: 1_700_000_000 } },
|
|
updatedAt: 1_700_000_100,
|
|
},
|
|
trustedReporters: [{ pubkey: "e".repeat(64), latest: 1_700_000_050 }],
|
|
adminWhitelist: false,
|
|
trustedMuted: false,
|
|
},
|
|
};
|
|
|
|
t.after(() => {
|
|
clearModerationOverride(video.id, { persist: false });
|
|
});
|
|
|
|
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(card.getRoot().dataset.autoplayPolicy, "blocked");
|
|
assert.equal(card.thumbnailEl.dataset.thumbnailState, "blurred");
|
|
assert.equal(card.getRoot().dataset.moderationReportCount, "3");
|
|
assert.ok(card.moderationBadgeEl);
|
|
assert.equal(card.moderationBadgeEl.dataset.moderationState, "hidden");
|
|
assert.equal(card.getRoot().dataset.moderationHidden, "true");
|
|
assert.equal(card.getRoot().dataset.moderationHideReason, "trusted-report-hide");
|
|
assert.equal(card.getRoot().dataset.moderationHideTrustedReportCount, "3");
|
|
assert.ok(card.hiddenSummaryEl);
|
|
assert.equal(card.hiddenSummaryEl.parentElement, card.getRoot());
|
|
assert.equal(card.anchorEl.hasAttribute("hidden"), true);
|
|
assert.equal(card.contentEl.hasAttribute("hidden"), true);
|
|
assert.ok(card.moderationActionButton);
|
|
|
|
card.onModerationOverride = ({ video: overrideVideo }) => {
|
|
app.videosMap.set(overrideVideo.id, overrideVideo);
|
|
app.currentVideo = overrideVideo;
|
|
setModerationOverride(overrideVideo.id, { showAnyway: true, updatedAt: Date.now() }, { persist: false });
|
|
app.decorateVideoModeration(overrideVideo);
|
|
card.refreshModerationUi();
|
|
return true;
|
|
};
|
|
|
|
card.moderationActionButton.click();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
const contextAfterOverride = normalizeVideoModerationContext(
|
|
card.video?.moderation,
|
|
);
|
|
assert.equal(contextAfterOverride.overrideActive, true);
|
|
assert.equal(contextAfterOverride.activeBlockAutoplay, false);
|
|
|
|
assert.equal(card.getRoot().dataset.autoplayPolicy, undefined);
|
|
assert.equal(card.getRoot().dataset.moderationOverride, "show-anyway");
|
|
assert.equal(card.thumbnailEl.dataset.thumbnailState, undefined);
|
|
assert.equal(card.moderationBadgeEl.dataset.moderationState, "override");
|
|
assert.equal(card.getRoot().dataset.moderationHidden, undefined);
|
|
assert.equal(card.getRoot().dataset.moderationHideReason, undefined);
|
|
assert.equal(card.getRoot().dataset.moderationHideTrustedReportCount, undefined);
|
|
assert.equal(card.anchorEl.hasAttribute("hidden"), false);
|
|
assert.equal(card.contentEl.hasAttribute("hidden"), false);
|
|
assert.ok(!card.hiddenSummaryEl || card.hiddenSummaryEl.parentElement !== card.getRoot());
|
|
assert.ok(card.moderationBadgeEl.textContent.includes("Showing despite"));
|
|
});
|
|
|
|
test(
|
|
"VideoCard hides content when hide metadata is present and override unmasks it",
|
|
async (t) => {
|
|
const { document } = setupDom(t);
|
|
withMockedNostrTools(t);
|
|
|
|
const app = await createModerationAppHarness();
|
|
app.moderationDecorator.updateSettings({ trustedSpamHideThreshold: 3 });
|
|
const videoId = "b".repeat(64);
|
|
|
|
const video = {
|
|
id: videoId,
|
|
title: "Hidden Clip",
|
|
pubkey: "c".repeat(64),
|
|
moderation: {
|
|
blockAutoplay: true,
|
|
blurThumbnail: true,
|
|
reportType: "spam",
|
|
trustedCount: 3,
|
|
trustedReporters: [
|
|
{ pubkey: "d".repeat(64), latest: 1_700_000_001 },
|
|
{ pubkey: "e".repeat(64), latest: 1_700_000_002 },
|
|
{ pubkey: "f".repeat(64), latest: 1_700_000_003 },
|
|
],
|
|
},
|
|
};
|
|
|
|
t.after(() => {
|
|
clearModerationOverride(video.id, { persist: false });
|
|
});
|
|
|
|
app.videosMap.set(video.id, video);
|
|
app.currentVideo = video;
|
|
app.decorateVideoModeration(video);
|
|
|
|
const card = new VideoCard({
|
|
document,
|
|
video,
|
|
formatters: {
|
|
formatTimeAgo: () => "moments ago",
|
|
},
|
|
helpers: {
|
|
isMagnetSupported: () => true,
|
|
},
|
|
});
|
|
|
|
document.body.appendChild(card.getRoot());
|
|
|
|
const contextBeforeOverride = normalizeVideoModerationContext(
|
|
card.video?.moderation,
|
|
);
|
|
assert.equal(contextBeforeOverride.activeHidden, true);
|
|
assert.equal(contextBeforeOverride.effectiveHideReason, "trusted-report-hide");
|
|
assert.equal(contextBeforeOverride.allowOverride, true);
|
|
|
|
assert.equal(card.getRoot().dataset.moderationHidden, "true");
|
|
assert.equal(card.getRoot().dataset.moderationHideReason, "trusted-report-hide");
|
|
assert.equal(card.getRoot().dataset.moderationHideTrustedReportCount, "3");
|
|
assert.ok(card.hiddenSummaryEl);
|
|
assert.equal(card.anchorEl.hasAttribute("hidden"), true);
|
|
assert.equal(card.contentEl.hasAttribute("hidden"), true);
|
|
assert.ok(card.moderationActionButton);
|
|
assert.ok(card.moderationBadgeEl.textContent.includes("Hidden"));
|
|
|
|
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.equal(contextAfterOverride.effectiveHideReason, "trusted-report-hide");
|
|
|
|
assert.equal(card.getRoot().dataset.moderationHidden, undefined);
|
|
assert.equal(card.getRoot().dataset.moderationOverride, "show-anyway");
|
|
assert.equal(card.getRoot().dataset.moderationHideReason, undefined);
|
|
assert.equal(card.getRoot().dataset.moderationHideTrustedReportCount, undefined);
|
|
assert.equal(card.anchorEl.hasAttribute("hidden"), false);
|
|
assert.equal(card.contentEl.hasAttribute("hidden"), false);
|
|
assert.ok(!card.hiddenSummaryEl || card.hiddenSummaryEl.parentElement !== card.getRoot());
|
|
assert.ok(card.moderationBadgeEl.textContent.includes("Showing despite"));
|
|
},
|
|
);
|
|
|
|
test("VideoCard blurs viewer-muted creators", async (t) => {
|
|
const { document } = setupDom(t);
|
|
withMockedNostrTools(t);
|
|
|
|
const app = await createModerationAppHarness();
|
|
const videoId = "d".repeat(64);
|
|
|
|
const video = {
|
|
id: videoId,
|
|
title: "Muted Creator Clip",
|
|
pubkey: "e".repeat(64),
|
|
moderation: {
|
|
viewerMuted: true,
|
|
blockAutoplay: true,
|
|
blurThumbnail: true,
|
|
blurReason: "viewer-mute",
|
|
trustedCount: 0,
|
|
trustedMuted: false,
|
|
trustedReporters: [],
|
|
},
|
|
};
|
|
|
|
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(card.getRoot().dataset.autoplayPolicy, "blocked");
|
|
assert.equal(card.thumbnailEl.dataset.thumbnailState, "blurred");
|
|
|
|
const context = normalizeVideoModerationContext(card.video?.moderation);
|
|
assert.equal(context.blurReason, "viewer-mute");
|
|
assert.equal(context.activeBlur, true);
|
|
assert.equal(context.activeBlockAutoplay, true);
|
|
assert.equal(context.trustedCount, 0);
|
|
assert.equal(card.getRoot().dataset.moderationReportCount, undefined);
|
|
assert.ok(card.moderationBadgeEl);
|
|
assert.equal(card.moderationBadgeEl.dataset.moderationState, "blocked");
|
|
});
|
|
|
|
test("VideoCard keeps a single trusted mute as a non-blocking ranking signal", async (t) => {
|
|
const { document } = setupDom(t);
|
|
withMockedNostrTools(t);
|
|
|
|
const app = await createModerationAppHarness();
|
|
app.getActiveModerationThresholds = () => ({
|
|
autoplayBlockThreshold: Number.POSITIVE_INFINITY,
|
|
blurThreshold: Number.POSITIVE_INFINITY,
|
|
trustedMuteHideThreshold: 2,
|
|
trustedSpamHideThreshold: Number.POSITIVE_INFINITY,
|
|
});
|
|
const videoId = "c".repeat(64);
|
|
|
|
const video = {
|
|
id: videoId,
|
|
title: "Muted Clip",
|
|
pubkey: "d".repeat(64),
|
|
moderation: {
|
|
trustedMuted: true,
|
|
trustedMuteCount: 1,
|
|
blockAutoplay: false,
|
|
blurThumbnail: false,
|
|
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(video.moderation.blurThumbnail, false);
|
|
assert.equal(video.moderation.blurReason, "");
|
|
assert.equal(
|
|
normalizeVideoModerationContext(card.video?.moderation).activeBlur,
|
|
false,
|
|
);
|
|
assert.equal(card.thumbnailEl.dataset.thumbnailState, undefined);
|
|
assert.equal(card.authorPicEl.dataset.visualState, undefined);
|
|
assert.equal(card.getRoot().dataset.autoplayPolicy, undefined);
|
|
assert.equal(card.moderationBadgeEl, null);
|
|
});
|
|
|
|
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.moderationDecorator.updateSettings({
|
|
trustedMuteHideThreshold: 1,
|
|
autoplayBlockThreshold: Number.POSITIVE_INFINITY,
|
|
blurThreshold: Number.POSITIVE_INFINITY,
|
|
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);
|
|
assert.equal(contextAfterHide.overrideActive, false);
|
|
assert.equal(contextAfterHide.activeHidden, true);
|
|
assert.equal(card.getRoot().dataset.moderationHidden, "true");
|
|
assert.equal(card.moderationBlockButton, null);
|
|
});
|
|
|
|
test("applyModerationContextDatasets clears blur when overrides are active", (t) => {
|
|
const { document } = setupDom(t);
|
|
|
|
const root = document.createElement("div");
|
|
const thumbnail = document.createElement("div");
|
|
const avatar = document.createElement("div");
|
|
|
|
const baselineContext = normalizeVideoModerationContext({
|
|
blurThumbnail: true,
|
|
reportType: "nudity",
|
|
trustedCount: 1,
|
|
original: { blockAutoplay: true },
|
|
});
|
|
|
|
applyModerationContextDatasets(baselineContext, {
|
|
root,
|
|
thumbnail,
|
|
avatar,
|
|
});
|
|
|
|
assert.equal(root.dataset.autoplayPolicy, "blocked");
|
|
assert.equal(root.dataset.moderationOverride, undefined);
|
|
assert.equal(thumbnail.dataset.thumbnailState, "blurred");
|
|
assert.equal(avatar.dataset.visualState, "blurred");
|
|
|
|
const overrideContext = normalizeVideoModerationContext({
|
|
blurThumbnail: true,
|
|
reportType: "nudity",
|
|
trustedCount: 1,
|
|
viewerOverride: { showAnyway: true },
|
|
original: { blockAutoplay: true },
|
|
});
|
|
|
|
applyModerationContextDatasets(overrideContext, {
|
|
root,
|
|
thumbnail,
|
|
avatar,
|
|
});
|
|
|
|
assert.equal(root.dataset.autoplayPolicy, undefined);
|
|
assert.equal(root.dataset.moderationOverride, "show-anyway");
|
|
assert.equal(thumbnail.dataset.thumbnailState, undefined);
|
|
assert.equal(avatar.dataset.visualState, undefined);
|
|
});
|
|
|
|
test(
|
|
"buildModerationBadgeText returns trusted contact block copy when autoplay block and trusted mute combine",
|
|
() => {
|
|
const context = {
|
|
originalBlockAutoplay: true,
|
|
trustedMuted: true,
|
|
};
|
|
|
|
assert.equal(
|
|
buildModerationBadgeText(context, { variant: "card" }),
|
|
"Content or user blocked by a trusted contact.",
|
|
);
|
|
assert.equal(
|
|
buildModerationBadgeText(context, { variant: "modal" }),
|
|
"Content or user blocked by a trusted contact.",
|
|
);
|
|
},
|
|
);
|