mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add tests for moderation block events and update QA fixtures
This commit is contained in:
1 parent
4ac5a776a6
commit
146a6ba7fc
5 files changed
+526
-39
No files matched your search
@@ -47,6 +47,7 @@
|
||||
</main>
|
||||
<script type="module">
|
||||
import { VideoCard } from "/js/ui/components/VideoCard.js";
|
||||
import moderationService from "/js/services/moderationService.js";
|
||||
|
||||
const STORAGE_KEY = "bitvid:moderation:fixture-overrides";
|
||||
const fixtures = [
|
||||
@@ -55,6 +56,7 @@
|
||||
heading: "Blur threshold (≥3 trusted nudity reports)",
|
||||
description:
|
||||
"Card should be blurred and autoplay disabled until the viewer overrides it.",
|
||||
thresholds: { blur: 3, autoplay: 2 },
|
||||
video: {
|
||||
id: "1".repeat(64),
|
||||
title: "Fixture: Blurred Preview",
|
||||
@@ -72,6 +74,10 @@
|
||||
{ name: "Mira F1", pubkey: "c".repeat(64), latest: 1_700_000_020 },
|
||||
{ name: "Omar F1", pubkey: "d".repeat(64), latest: 1_700_000_030 },
|
||||
],
|
||||
blockToggle: {
|
||||
label: "Simulate viewer blocking Mira F1",
|
||||
pubkey: "c".repeat(64),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "autoplay-threshold",
|
||||
@@ -140,6 +146,89 @@
|
||||
},
|
||||
];
|
||||
|
||||
const blockedReportersByVideo = new Map();
|
||||
|
||||
function getBlockedSet(videoId) {
|
||||
if (typeof videoId !== "string" || !videoId) {
|
||||
return null;
|
||||
}
|
||||
return blockedReportersByVideo.get(videoId) || null;
|
||||
}
|
||||
|
||||
function setReporterBlocked(videoId, pubkey, blocked) {
|
||||
if (typeof videoId !== "string" || !videoId || typeof pubkey !== "string" || !pubkey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let entry = blockedReportersByVideo.get(videoId);
|
||||
if (blocked) {
|
||||
if (!entry) {
|
||||
entry = new Set();
|
||||
blockedReportersByVideo.set(videoId, entry);
|
||||
}
|
||||
entry.add(pubkey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.delete(pubkey);
|
||||
if (!entry.size) {
|
||||
blockedReportersByVideo.delete(videoId);
|
||||
}
|
||||
}
|
||||
|
||||
function isReporterBlocked(videoId, pubkey) {
|
||||
const entry = getBlockedSet(videoId);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return entry.has(pubkey);
|
||||
}
|
||||
|
||||
function getActiveReporters(fixture, videoId) {
|
||||
const reporters = Array.isArray(fixture?.reporters) ? fixture.reporters : [];
|
||||
if (!reporters.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entry = getBlockedSet(videoId);
|
||||
if (!entry || !entry.size) {
|
||||
return reporters.slice();
|
||||
}
|
||||
|
||||
return reporters.filter((reporter) => {
|
||||
const key = typeof reporter?.pubkey === "string" ? reporter.pubkey : "";
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
return !entry.has(key);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeFixtureReporters(reporters) {
|
||||
if (!Array.isArray(reporters)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return reporters
|
||||
.map((reporter) => {
|
||||
if (!reporter || typeof reporter !== "object") {
|
||||
return null;
|
||||
}
|
||||
const pubkey = typeof reporter.pubkey === "string" ? reporter.pubkey : "";
|
||||
if (!pubkey) {
|
||||
return null;
|
||||
}
|
||||
const latest = Number.isFinite(reporter.latest) ? Math.floor(reporter.latest) : 0;
|
||||
const name = typeof reporter.name === "string" ? reporter.name : "";
|
||||
return { name, pubkey, latest };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
window.__MODERATION_FIXTURE_STORAGE_KEY__ = STORAGE_KEY;
|
||||
|
||||
function loadOverrides() {
|
||||
@@ -163,26 +252,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
function buildSummary(fixture) {
|
||||
function buildSummary(fixture, videoId, sanitizedReporters = null) {
|
||||
if (!fixture?.moderation?.reportType) {
|
||||
return null;
|
||||
}
|
||||
const latest = Array.isArray(fixture.reporters)
|
||||
? fixture.reporters.reduce(
|
||||
(max, reporter) =>
|
||||
Number.isFinite(reporter?.latest)
|
||||
? Math.max(max, Math.floor(reporter.latest))
|
||||
: max,
|
||||
0,
|
||||
)
|
||||
: 0;
|
||||
|
||||
const reporters =
|
||||
Array.isArray(sanitizedReporters) && sanitizedReporters
|
||||
? sanitizedReporters
|
||||
: normalizeFixtureReporters(getActiveReporters(fixture, videoId));
|
||||
|
||||
const latest = reporters.reduce((max, reporter) => Math.max(max, reporter.latest), 0);
|
||||
|
||||
return {
|
||||
eventId: fixture.video.id,
|
||||
totalTrusted: fixture.moderation.trustedCount,
|
||||
totalTrusted: reporters.length,
|
||||
types: {
|
||||
[fixture.moderation.reportType]: {
|
||||
trusted: fixture.moderation.trustedCount,
|
||||
total: fixture.moderation.trustedCount,
|
||||
trusted: reporters.length,
|
||||
total: reporters.length,
|
||||
latest,
|
||||
},
|
||||
},
|
||||
@@ -192,28 +280,46 @@
|
||||
|
||||
function buildModerationState(fixture, overrides, videoId) {
|
||||
const overrideEntry = overrides?.[videoId];
|
||||
const activeReporters = getActiveReporters(fixture, videoId);
|
||||
const sanitizedReporters = normalizeFixtureReporters(activeReporters);
|
||||
const thresholds =
|
||||
fixture.thresholds && typeof fixture.thresholds === "object"
|
||||
? { blur: 3, autoplay: 2, ...fixture.thresholds }
|
||||
: { blur: 3, autoplay: 2 };
|
||||
const autoplayThreshold = Number.isFinite(thresholds.autoplay)
|
||||
? Math.max(0, Math.floor(thresholds.autoplay))
|
||||
: 2;
|
||||
const blurThreshold = Number.isFinite(thresholds.blur)
|
||||
? Math.max(0, Math.floor(thresholds.blur))
|
||||
: 3;
|
||||
const initialBlock = fixture.moderation.blockAutoplay === true;
|
||||
const initialBlur = fixture.moderation.blurThumbnail === true;
|
||||
const trustedCount = sanitizedReporters.length;
|
||||
|
||||
const reporterDisplayNames = sanitizedReporters
|
||||
.map((reporter) => reporter.name)
|
||||
.filter((value) => typeof value === "string" && value);
|
||||
|
||||
const reporterPubkeys = sanitizedReporters.map((reporter) => reporter.pubkey);
|
||||
|
||||
const trustedReporterEntries = sanitizedReporters.map((reporter) => ({
|
||||
pubkey: reporter.pubkey,
|
||||
latest: reporter.latest,
|
||||
}));
|
||||
|
||||
const base = {
|
||||
reportType: fixture.moderation.reportType,
|
||||
trustedCount: fixture.moderation.trustedCount,
|
||||
reporterDisplayNames: Array.isArray(fixture.reporters)
|
||||
? fixture.reporters.map((reporter) => reporter.name)
|
||||
: [],
|
||||
reporterPubkeys: Array.isArray(fixture.reporters)
|
||||
? fixture.reporters.map((reporter) => reporter.pubkey)
|
||||
: [],
|
||||
trustedReporters: Array.isArray(fixture.reporters)
|
||||
? fixture.reporters.map((reporter) => ({
|
||||
pubkey: reporter.pubkey,
|
||||
latest: reporter.latest,
|
||||
}))
|
||||
: [],
|
||||
trustedCount,
|
||||
reporterDisplayNames,
|
||||
reporterPubkeys,
|
||||
trustedReporters: trustedReporterEntries,
|
||||
original: {
|
||||
blockAutoplay: fixture.moderation.blockAutoplay === true,
|
||||
blurThumbnail: fixture.moderation.blurThumbnail === true,
|
||||
blockAutoplay: initialBlock,
|
||||
blurThumbnail: initialBlur,
|
||||
},
|
||||
};
|
||||
|
||||
const summary = buildSummary(fixture);
|
||||
const summary = buildSummary(fixture, videoId, sanitizedReporters);
|
||||
if (summary) {
|
||||
base.summary = summary;
|
||||
}
|
||||
@@ -247,8 +353,8 @@
|
||||
base.blockAutoplay = false;
|
||||
base.blurThumbnail = false;
|
||||
} else {
|
||||
base.blockAutoplay = fixture.moderation.blockAutoplay === true;
|
||||
base.blurThumbnail = fixture.moderation.blurThumbnail === true;
|
||||
base.blockAutoplay = initialBlock && trustedCount >= autoplayThreshold;
|
||||
base.blurThumbnail = initialBlur && trustedCount >= blurThreshold;
|
||||
}
|
||||
|
||||
return base;
|
||||
@@ -304,22 +410,24 @@
|
||||
},
|
||||
});
|
||||
|
||||
const refreshModeration = () => {
|
||||
card.video.moderation = buildModerationState(fixture, overrides, video.id);
|
||||
if (typeof card.refreshModerationUi === "function") {
|
||||
card.refreshModerationUi();
|
||||
}
|
||||
};
|
||||
|
||||
if (fixture.enableOverride) {
|
||||
card.onModerationOverride = () => {
|
||||
overrides = setOverride(overrides, video.id, { showAnyway: true });
|
||||
card.video.moderation = buildModerationState(
|
||||
fixture,
|
||||
overrides,
|
||||
video.id,
|
||||
);
|
||||
card.refreshModerationUi();
|
||||
refreshModeration();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
const root = card.getRoot();
|
||||
root.dataset.testId = fixture.key;
|
||||
return { card, root };
|
||||
return { card, root, refreshModeration };
|
||||
}
|
||||
|
||||
function renderFixtures() {
|
||||
@@ -343,8 +451,51 @@
|
||||
section.appendChild(description);
|
||||
}
|
||||
|
||||
const { root: cardRoot } = createCard(fixture, overrides);
|
||||
const { root: cardRoot, refreshModeration } = createCard(fixture, overrides);
|
||||
section.appendChild(cardRoot);
|
||||
|
||||
if (fixture.blockToggle && fixture.blockToggle.pubkey) {
|
||||
const toggleWrapper = document.createElement("div");
|
||||
toggleWrapper.style.marginTop = "0.75rem";
|
||||
toggleWrapper.style.fontSize = "0.875rem";
|
||||
toggleWrapper.style.color = "#cbd5f5";
|
||||
|
||||
const label = document.createElement("label");
|
||||
label.style.display = "inline-flex";
|
||||
label.style.alignItems = "center";
|
||||
label.style.gap = "0.5rem";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "checkbox";
|
||||
input.checked = isReporterBlocked(fixture.video.id, fixture.blockToggle.pubkey);
|
||||
input.addEventListener("change", () => {
|
||||
const checked = input.checked;
|
||||
setReporterBlocked(fixture.video.id, fixture.blockToggle.pubkey, checked);
|
||||
const action = checked ? "block" : "unblock";
|
||||
try {
|
||||
const detail = { action, targetPubkey: fixture.blockToggle.pubkey };
|
||||
const maybePromise = moderationService?.queueUserBlockRefresh?.(detail);
|
||||
if (maybePromise && typeof maybePromise.then === "function") {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
} catch {
|
||||
/* ignore fixture errors */
|
||||
}
|
||||
refreshModeration();
|
||||
});
|
||||
|
||||
const text = document.createElement("span");
|
||||
text.textContent =
|
||||
typeof fixture.blockToggle.label === "string"
|
||||
? fixture.blockToggle.label
|
||||
: "Viewer blocks reporter";
|
||||
|
||||
label.appendChild(input);
|
||||
label.appendChild(text);
|
||||
toggleWrapper.appendChild(label);
|
||||
section.appendChild(toggleWrapper);
|
||||
}
|
||||
|
||||
root.appendChild(section);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import "../test-helpers/setup-localstorage.mjs";
|
||||
import {
|
||||
withMockedNostrTools,
|
||||
createModerationAppHarness,
|
||||
} from "../helpers/moderation-test-helpers.mjs";
|
||||
|
||||
const REPORTER_HEX = "b".repeat(64);
|
||||
const MUTER_HEX = "c".repeat(64);
|
||||
|
||||
function buildModerationState() {
|
||||
return {
|
||||
blockAutoplay: true,
|
||||
blurThumbnail: true,
|
||||
reportType: "nudity",
|
||||
trustedCount: 2,
|
||||
trustedReporters: [{ pubkey: REPORTER_HEX, latest: 1_700_000_000 }],
|
||||
trustedMuted: true,
|
||||
trustedMuters: [MUTER_HEX],
|
||||
trustedMuteCount: 1,
|
||||
};
|
||||
}
|
||||
|
||||
test("handleModerationOverride decorates stored and current videos then refreshes UI", async (t) => {
|
||||
withMockedNostrTools(t);
|
||||
|
||||
const hadNavigator = typeof globalThis.navigator !== "undefined";
|
||||
if (!hadNavigator) {
|
||||
globalThis.navigator = { userAgent: "node-test" };
|
||||
t.after(() => {
|
||||
delete globalThis.navigator;
|
||||
});
|
||||
}
|
||||
|
||||
const app = await createModerationAppHarness();
|
||||
const originalDecorate = app.decorateVideoModeration;
|
||||
const decoratedTargets = [];
|
||||
app.decorateVideoModeration = function decorateSpy(video) {
|
||||
decoratedTargets.push(video);
|
||||
return originalDecorate.call(this, video);
|
||||
};
|
||||
|
||||
const videoId = "a".repeat(64);
|
||||
const incomingVideo = { id: videoId, moderation: buildModerationState() };
|
||||
const storedVideo = { id: videoId, moderation: buildModerationState() };
|
||||
const currentVideo = { id: videoId, moderation: buildModerationState() };
|
||||
|
||||
app.videosMap.set(videoId, storedVideo);
|
||||
app.currentVideo = currentVideo;
|
||||
|
||||
let refreshCount = 0;
|
||||
const card = {
|
||||
refreshModerationUi() {
|
||||
refreshCount += 1;
|
||||
},
|
||||
};
|
||||
|
||||
const result = app.handleModerationOverride({ video: incomingVideo, card });
|
||||
|
||||
assert.equal(result, true);
|
||||
assert.equal(refreshCount, 1);
|
||||
assert.deepEqual(decoratedTargets, [storedVideo, currentVideo]);
|
||||
|
||||
assert.equal(storedVideo.moderation.viewerOverride?.showAnyway, true);
|
||||
assert.equal(storedVideo.moderation.blockAutoplay, false);
|
||||
assert.equal(storedVideo.moderation.blurThumbnail, false);
|
||||
assert.equal(currentVideo.moderation.viewerOverride?.showAnyway, true);
|
||||
assert.equal(currentVideo.moderation.blockAutoplay, false);
|
||||
assert.equal(currentVideo.moderation.blurThumbnail, false);
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ModerationService } from "../js/services/moderationService.js";
|
||||
import { USER_BLOCK_EVENTS } from "../js/userBlocks.js";
|
||||
|
||||
const nip19 = {
|
||||
npubEncode(hex) {
|
||||
@@ -101,6 +102,124 @@ test("trusted report summaries respect personal blocks and admin lists", async (
|
||||
assert.equal(service.trustedReportCount(eventId, "nudity"), 2);
|
||||
});
|
||||
|
||||
test("user block updates recompute summaries and emit notifications", async (t) => {
|
||||
const blockedHex = "1".repeat(64);
|
||||
const trustedHex = "2".repeat(64);
|
||||
const randomHex = "3".repeat(64);
|
||||
const eventId = "4".repeat(64);
|
||||
|
||||
class FakeUserBlocks {
|
||||
constructor() {
|
||||
this.blocked = new Set();
|
||||
this.listeners = new Set();
|
||||
}
|
||||
|
||||
async ensureLoaded() {}
|
||||
|
||||
isBlocked(pubkey) {
|
||||
return this.blocked.has(pubkey);
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (eventName !== USER_BLOCK_EVENTS.CHANGE || typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
this.listeners.add(handler);
|
||||
return () => {
|
||||
this.listeners.delete(handler);
|
||||
};
|
||||
}
|
||||
|
||||
emitChange(detail) {
|
||||
for (const handler of Array.from(this.listeners)) {
|
||||
handler(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userBlocks = new FakeUserBlocks();
|
||||
const service = new ModerationService({ logger: () => {}, userBlocks });
|
||||
|
||||
service.trustedContacts = new Set([blockedHex, trustedHex]);
|
||||
|
||||
const reports = new Map();
|
||||
reports.set(
|
||||
blockedHex,
|
||||
new Map([["nudity", { created_at: 200 }]]),
|
||||
);
|
||||
reports.set(
|
||||
trustedHex,
|
||||
new Map([["nudity", { created_at: 150 }]]),
|
||||
);
|
||||
reports.set(
|
||||
randomHex,
|
||||
new Map([["nudity", { created_at: 100 }]]),
|
||||
);
|
||||
|
||||
service.reportEvents.set(eventId, reports);
|
||||
service.recomputeSummaryForEvent(eventId);
|
||||
|
||||
const initialSummary = service.getTrustedReportSummary(eventId);
|
||||
assert.equal(initialSummary.totalTrusted, 2);
|
||||
assert.equal(initialSummary.types.nudity.total, 3);
|
||||
assert.equal(initialSummary.types.nudity.trusted, 2);
|
||||
|
||||
const initialReporters = service.getTrustedReporters(eventId, "nudity");
|
||||
assert.deepEqual(
|
||||
initialReporters.map((entry) => entry.pubkey),
|
||||
[blockedHex, trustedHex],
|
||||
);
|
||||
|
||||
const blockEvent = new Promise((resolve) => {
|
||||
const unsubscribe = service.on("user-blocks", (detail) => {
|
||||
unsubscribe?.();
|
||||
resolve(detail);
|
||||
});
|
||||
});
|
||||
|
||||
userBlocks.blocked.add(blockedHex);
|
||||
userBlocks.emitChange({ action: "block", targetPubkey: blockedHex });
|
||||
|
||||
const blockDetail = await blockEvent;
|
||||
assert.equal(blockDetail.action, "block");
|
||||
assert.equal(blockDetail.targetPubkey, blockedHex);
|
||||
|
||||
const afterBlockSummary = service.getTrustedReportSummary(eventId);
|
||||
assert.equal(afterBlockSummary.totalTrusted, 1);
|
||||
assert.equal(afterBlockSummary.types.nudity.total, 2);
|
||||
assert.equal(afterBlockSummary.types.nudity.trusted, 1);
|
||||
assert.equal(service.trustedReportCount(eventId, "nudity"), 1);
|
||||
|
||||
const reportersAfterBlock = service.getTrustedReporters(eventId, "nudity");
|
||||
assert.deepEqual(reportersAfterBlock.map((entry) => entry.pubkey), [trustedHex]);
|
||||
|
||||
const unblockEvent = new Promise((resolve) => {
|
||||
const unsubscribe = service.on("user-blocks", (detail) => {
|
||||
unsubscribe?.();
|
||||
resolve(detail);
|
||||
});
|
||||
});
|
||||
|
||||
userBlocks.blocked.delete(blockedHex);
|
||||
userBlocks.emitChange({ action: "unblock", targetPubkey: blockedHex });
|
||||
|
||||
const unblockDetail = await unblockEvent;
|
||||
assert.equal(unblockDetail.action, "unblock");
|
||||
assert.equal(unblockDetail.targetPubkey, blockedHex);
|
||||
|
||||
const afterUnblockSummary = service.getTrustedReportSummary(eventId);
|
||||
assert.equal(afterUnblockSummary.totalTrusted, 2);
|
||||
assert.equal(afterUnblockSummary.types.nudity.total, 3);
|
||||
assert.equal(afterUnblockSummary.types.nudity.trusted, 2);
|
||||
assert.equal(service.trustedReportCount(eventId, "nudity"), 2);
|
||||
|
||||
const reportersAfterUnblock = service.getTrustedReporters(eventId, "nudity");
|
||||
assert.deepEqual(
|
||||
reportersAfterUnblock.map((entry) => entry.pubkey),
|
||||
[blockedHex, trustedHex],
|
||||
);
|
||||
});
|
||||
|
||||
test("trusted mute aggregation tracks F1 mute lists", () => {
|
||||
const service = new ModerationService({ logger: () => {} });
|
||||
|
||||
|
||||
@@ -295,3 +295,112 @@ test("moderation stage propagates whitelist, muters, and threshold updates", asy
|
||||
assert.equal(updatedMuted.video.moderation.blockAutoplay, false);
|
||||
assert.equal(updatedMuted.video.moderation.blurThumbnail, false);
|
||||
});
|
||||
|
||||
test("moderation stage clears cached reporters and muters after service signals", async () => {
|
||||
const reporterHex = "d".repeat(64);
|
||||
const muterHex = "e".repeat(64);
|
||||
const authorHex = "f".repeat(64);
|
||||
const videoId = "9".repeat(64);
|
||||
|
||||
const listeners = new Map();
|
||||
|
||||
const service = {
|
||||
reporters: [{ pubkey: reporterHex, latest: 1_700_000_100 }],
|
||||
muters: [muterHex],
|
||||
async refreshViewerFromClient() {},
|
||||
async setActiveEventIds() {},
|
||||
getAdminListSnapshot() {
|
||||
return { whitelist: new Set(), whitelistHex: new Set(), blacklist: new Set(), blacklistHex: new Set() };
|
||||
},
|
||||
getAccessControlStatus(identifier) {
|
||||
return { hex: identifier, whitelisted: false, blacklisted: false };
|
||||
},
|
||||
getTrustedReportSummary(targetId) {
|
||||
const trusted = this.reporters.length;
|
||||
return {
|
||||
eventId: targetId,
|
||||
totalTrusted: trusted,
|
||||
types: {
|
||||
nudity: {
|
||||
trusted,
|
||||
total: trusted,
|
||||
latest: this.reporters[0]?.latest ?? 0,
|
||||
},
|
||||
},
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
},
|
||||
trustedReportCount() {
|
||||
return this.reporters.length;
|
||||
},
|
||||
getTrustedReporters() {
|
||||
return this.reporters.slice();
|
||||
},
|
||||
isAuthorMutedByTrusted() {
|
||||
return this.muters.length > 0;
|
||||
},
|
||||
getTrustedMutersForAuthor() {
|
||||
return this.muters.slice();
|
||||
},
|
||||
on(eventName, handler) {
|
||||
if (typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
if (!listeners.has(eventName)) {
|
||||
listeners.set(eventName, new Set());
|
||||
}
|
||||
const bucket = listeners.get(eventName);
|
||||
bucket.add(handler);
|
||||
return () => {
|
||||
bucket.delete(handler);
|
||||
if (!bucket.size) {
|
||||
listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
},
|
||||
emit(eventName, detail) {
|
||||
const bucket = listeners.get(eventName);
|
||||
if (!bucket) {
|
||||
return;
|
||||
}
|
||||
for (const handler of Array.from(bucket)) {
|
||||
handler(detail);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const stage = createModerationStage({ service, reportType: "nudity" });
|
||||
|
||||
const context = {
|
||||
addWhy() {},
|
||||
log() {},
|
||||
};
|
||||
|
||||
const items = [{ video: { id: videoId, pubkey: authorHex }, metadata: {} }];
|
||||
|
||||
const firstPass = await stage(items, context);
|
||||
const firstVideo = firstPass[0].video;
|
||||
|
||||
assert.equal(firstVideo.moderation.trustedCount, 1);
|
||||
assert.equal(firstVideo.moderation.trustedMuted, true);
|
||||
assert.deepEqual(firstVideo.moderation.trustedMuters, [muterHex]);
|
||||
assert.deepEqual(firstVideo.moderation.trustedReporters, [{ pubkey: reporterHex, latest: 1_700_000_100 }]);
|
||||
|
||||
service.reporters = [];
|
||||
service.muters = [];
|
||||
service.emit("user-blocks", { action: "block", targetPubkey: reporterHex });
|
||||
service.emit("trusted-mutes", { total: 0 });
|
||||
|
||||
const secondPass = await stage(firstPass, context);
|
||||
const updatedVideo = secondPass[0].video;
|
||||
const updatedMetadata = secondPass[0].metadata.moderation;
|
||||
|
||||
assert.equal(updatedVideo.moderation.trustedCount, 0);
|
||||
assert.equal(updatedVideo.moderation.trustedMuted, false);
|
||||
assert.equal("trustedMuters" in updatedVideo.moderation, false);
|
||||
assert.equal("trustedReporters" in updatedVideo.moderation, false);
|
||||
assert.ok(Array.isArray(updatedMetadata.trustedReporters));
|
||||
assert.equal(updatedMetadata.trustedReporters.length, 0);
|
||||
assert.ok(Array.isArray(updatedMetadata.trustedMuters));
|
||||
assert.equal(updatedMetadata.trustedMuters.length, 0);
|
||||
});
|
||||
@@ -36,6 +36,20 @@ async function setupModal({ lazyLoad = false } = {}) {
|
||||
globalThis.location = window.location;
|
||||
globalThis.KeyboardEvent = window.KeyboardEvent;
|
||||
globalThis.MouseEvent = window.MouseEvent;
|
||||
const hadWebSocket = typeof globalThis.WebSocket !== "undefined";
|
||||
if (typeof window.scrollTo !== "function") {
|
||||
window.scrollTo = () => {};
|
||||
}
|
||||
globalThis.scrollTo = window.scrollTo;
|
||||
if (!hadWebSocket) {
|
||||
globalThis.WebSocket = class {
|
||||
constructor() {}
|
||||
close() {}
|
||||
addEventListener() {}
|
||||
removeEventListener() {}
|
||||
send() {}
|
||||
};
|
||||
}
|
||||
|
||||
applyDesignSystemAttributes(document);
|
||||
|
||||
@@ -119,6 +133,10 @@ async function setupModal({ lazyLoad = false } = {}) {
|
||||
delete globalThis.location;
|
||||
delete globalThis.KeyboardEvent;
|
||||
delete globalThis.MouseEvent;
|
||||
delete globalThis.scrollTo;
|
||||
if (!hadWebSocket) {
|
||||
delete globalThis.WebSocket;
|
||||
}
|
||||
if (restoreFetch) {
|
||||
restoreFetch();
|
||||
}
|
||||
@@ -302,6 +320,20 @@ async function setupPlaybackHarness() {
|
||||
globalThis.KeyboardEvent = window.KeyboardEvent;
|
||||
globalThis.MouseEvent = window.MouseEvent;
|
||||
globalThis.self = window;
|
||||
const hadHarnessWebSocket = typeof globalThis.WebSocket !== "undefined";
|
||||
if (typeof window.scrollTo !== "function") {
|
||||
window.scrollTo = () => {};
|
||||
}
|
||||
globalThis.scrollTo = window.scrollTo;
|
||||
if (!hadHarnessWebSocket) {
|
||||
globalThis.WebSocket = class {
|
||||
constructor() {}
|
||||
close() {}
|
||||
addEventListener() {}
|
||||
removeEventListener() {}
|
||||
send() {}
|
||||
};
|
||||
}
|
||||
|
||||
window.NostrTools = {
|
||||
nip19: {
|
||||
@@ -383,6 +415,10 @@ async function setupPlaybackHarness() {
|
||||
delete globalThis.KeyboardEvent;
|
||||
delete globalThis.MouseEvent;
|
||||
delete globalThis.self;
|
||||
delete globalThis.scrollTo;
|
||||
if (!hadHarnessWebSocket) {
|
||||
delete globalThis.WebSocket;
|
||||
}
|
||||
};
|
||||
|
||||
return { app, modalStub, playbackRecords, window, cleanup };
|
||||
|
||||
Reference in new issue
Block a user