Extract moderation helpers for video card

This commit is contained in:
thePR0M3TH3AN committed 2025-10-25 18:01:57 -04:00
1 parent 37c1238dc2
commit 9b18c9b0ba
3 files changed
+410 -271

No files matched your search

+11 -264
View File
@@ -1,6 +1,10 @@
import { normalizeDesignSystemContext } from "../../designSystem.js";
import { updateVideoCardSourceVisibility } from "../../utils/cardSourceVisibility.js";
import { userLogger } from "../../utils/logger.js";
import {
applyModerationContextDatasets,
normalizeVideoModerationContext,
} from "../moderationUiHelpers.js";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
@@ -974,162 +978,7 @@ export class VideoCard {
}
getModerationContext() {
const moderation =
this.video?.moderation && typeof this.video.moderation === "object"
? this.video.moderation
: null;
const summary =
moderation?.summary && typeof moderation.summary === "object"
? moderation.summary
: null;
let reportType = "";
if (typeof moderation?.reportType === "string" && moderation.reportType.trim()) {
reportType = moderation.reportType.trim().toLowerCase();
}
if (!reportType && summary && summary.types && typeof summary.types === "object") {
for (const [type, stats] of Object.entries(summary.types)) {
if (stats && Number.isFinite(stats.trusted) && Math.floor(stats.trusted) > 0) {
reportType = String(type).toLowerCase();
break;
}
}
}
let trustedCount = Number.isFinite(moderation?.trustedCount)
? Math.max(0, Math.floor(moderation.trustedCount))
: 0;
if (!trustedCount && summary && summary.types && typeof summary.types === "object") {
for (const stats of Object.values(summary.types)) {
if (stats && Number.isFinite(stats.trusted)) {
trustedCount = Math.max(trustedCount, Math.floor(stats.trusted));
}
}
}
const reporterDisplayNames = Array.isArray(moderation?.reporterDisplayNames)
? moderation.reporterDisplayNames
.map((name) => (typeof name === "string" ? name.trim() : ""))
.filter(Boolean)
: [];
const trustedMuted = moderation?.trustedMuted === true;
let trustedMuteCount = Number.isFinite(moderation?.trustedMuteCount)
? Math.max(0, Math.floor(moderation.trustedMuteCount))
: 0;
if (!trustedMuteCount && Array.isArray(moderation?.trustedMuters)) {
const muters = moderation.trustedMuters
.map((value) => (typeof value === "string" ? value.trim() : ""))
.filter(Boolean);
trustedMuteCount = muters.length;
}
const trustedMuteDisplayNames = Array.isArray(moderation?.trustedMuterDisplayNames)
? moderation.trustedMuterDisplayNames
.map((name) => (typeof name === "string" ? name.trim() : ""))
.filter(Boolean)
: [];
const original =
moderation?.original && typeof moderation.original === "object"
? moderation.original
: {};
const activeHidden = moderation?.hidden === true;
const originalHidden = original.hidden === true;
const hideReasonActive =
typeof moderation?.hideReason === "string" ? moderation.hideReason.trim() : "";
const hideBypassActive =
typeof moderation?.hideBypass === "string" ? moderation.hideBypass.trim() : "";
const originalHideReason =
typeof original.hideReason === "string" ? original.hideReason.trim() : "";
const originalHideBypass =
typeof original.hideBypass === "string" ? original.hideBypass.trim() : "";
const originalHideTriggered = original.hideTriggered === true;
const normalizeHideCounts = (input) => {
if (!input || typeof input !== "object") {
return null;
}
const normalized = {};
let hasValue = false;
if (Number.isFinite(input.trustedMuteCount)) {
normalized.trustedMuteCount = Math.max(0, Math.floor(input.trustedMuteCount));
hasValue = true;
}
if (Number.isFinite(input.trustedReportCount)) {
normalized.trustedReportCount = Math.max(0, Math.floor(input.trustedReportCount));
hasValue = true;
}
return hasValue ? normalized : null;
};
const baseHideCounts = {
trustedMuteCount,
trustedReportCount: trustedCount,
};
const activeHideCounts =
normalizeHideCounts(moderation?.hideCounts) ||
(activeHidden || hideReasonActive ? { ...baseHideCounts } : null);
const originalHideCounts =
normalizeHideCounts(original?.hideCounts) ||
(originalHideTriggered ? { ...baseHideCounts } : null);
const effectiveHideReason = hideReasonActive || originalHideReason;
const effectiveHideCounts = activeHideCounts || originalHideCounts;
const blurReason =
typeof moderation?.blurReason === "string" ? moderation.blurReason.trim() : "";
const originalBlurReason =
typeof original?.blurReason === "string" ? original.blurReason.trim() : "";
const context = {
reportType,
friendlyType: reportType ? reportType.replace(/[_-]+/g, " ").trim() : "",
trustedCount,
reporterDisplayNames,
trustedMuted,
trustedMuteCount,
trustedMuteDisplayNames,
blurReason,
originalBlurReason,
originalBlur: original.blurThumbnail === true,
originalBlockAutoplay: original.blockAutoplay === true,
activeBlur: moderation?.blurThumbnail === true,
activeBlockAutoplay: moderation?.blockAutoplay === true,
overrideActive: moderation?.viewerOverride?.showAnyway === true,
activeHidden,
originalHidden,
originalHideTriggered,
hideReason: hideReasonActive,
hideCounts: activeHideCounts,
hideBypass: hideBypassActive,
originalHideReason,
originalHideCounts,
originalHideBypass,
effectiveHideReason,
effectiveHideCounts,
};
context.shouldShow =
context.originalBlur ||
context.originalBlockAutoplay ||
context.trustedCount > 0 ||
context.trustedMuted ||
context.overrideActive ||
context.originalHidden ||
context.activeHidden ||
context.originalHideTriggered;
context.allowOverride =
context.originalBlur || context.originalBlockAutoplay || context.originalHidden;
return context;
return normalizeVideoModerationContext(this.video?.moderation);
}
buildModerationReasonText(context) {
@@ -2372,114 +2221,12 @@ export class VideoCard {
}
applyModerationDatasets(context = this.getModerationContext()) {
if (!this.root) {
return;
}
if (context.originalBlockAutoplay && !context.overrideActive) {
this.root.dataset.autoplayPolicy = "blocked";
} else if (this.root.dataset.autoplayPolicy) {
delete this.root.dataset.autoplayPolicy;
}
if (context.overrideActive) {
this.root.dataset.moderationOverride = "show-anyway";
} else if (this.root.dataset.moderationOverride) {
delete this.root.dataset.moderationOverride;
}
if (this.thumbnailEl && !this.shouldMaskNsfwForOwner) {
if (context.activeBlur) {
this.thumbnailEl.dataset.thumbnailState = "blurred";
} else if (this.thumbnailEl.dataset.thumbnailState === "blurred") {
delete this.thumbnailEl.dataset.thumbnailState;
}
}
if (this.authorPicEl && !this.shouldMaskNsfwForOwner) {
if (context.activeBlur) {
this.authorPicEl.dataset.visualState = "blurred";
} else if (this.authorPicEl.dataset.visualState) {
delete this.authorPicEl.dataset.visualState;
}
}
const reportCount = Math.max(0, Number(context.trustedCount) || 0);
if (reportCount > 0) {
this.root.dataset.moderationReportCount = String(reportCount);
if (context.reportType) {
this.root.dataset.moderationReportType = context.reportType;
} else if (this.root.dataset.moderationReportType) {
delete this.root.dataset.moderationReportType;
}
} else {
if (this.root.dataset.moderationReportType) {
delete this.root.dataset.moderationReportType;
}
if (this.root.dataset.moderationReportCount) {
delete this.root.dataset.moderationReportCount;
}
}
if (context.trustedMuted) {
this.root.dataset.moderationTrustedMute = "true";
const muteCount = Math.max(0, Number(context.trustedMuteCount) || 0);
if (muteCount > 0) {
this.root.dataset.moderationTrustedMuteCount = String(muteCount);
} else if (this.root.dataset.moderationTrustedMuteCount) {
delete this.root.dataset.moderationTrustedMuteCount;
}
} else {
if (this.root.dataset.moderationTrustedMute) {
delete this.root.dataset.moderationTrustedMute;
}
if (this.root.dataset.moderationTrustedMuteCount) {
delete this.root.dataset.moderationTrustedMuteCount;
}
}
if (context.activeHidden && !context.overrideActive) {
this.root.dataset.moderationHidden = "true";
const reason = context.effectiveHideReason || context.hideReason;
if (reason) {
this.root.dataset.moderationHideReason = reason;
} else if (this.root.dataset.moderationHideReason) {
delete this.root.dataset.moderationHideReason;
}
const counts = context.hideCounts || context.effectiveHideCounts || null;
const muteCount = counts && Number.isFinite(counts.trustedMuteCount)
? Math.max(0, Number(counts.trustedMuteCount))
: null;
const reportCountHide = counts && Number.isFinite(counts.trustedReportCount)
? Math.max(0, Number(counts.trustedReportCount))
: null;
if (muteCount !== null) {
this.root.dataset.moderationHideTrustedMuteCount = String(muteCount);
} else if (this.root.dataset.moderationHideTrustedMuteCount) {
delete this.root.dataset.moderationHideTrustedMuteCount;
}
if (reportCountHide !== null) {
this.root.dataset.moderationHideTrustedReportCount = String(reportCountHide);
} else if (this.root.dataset.moderationHideTrustedReportCount) {
delete this.root.dataset.moderationHideTrustedReportCount;
}
} else {
if (this.root.dataset.moderationHidden) {
delete this.root.dataset.moderationHidden;
}
if (this.root.dataset.moderationHideReason) {
delete this.root.dataset.moderationHideReason;
}
if (this.root.dataset.moderationHideTrustedMuteCount) {
delete this.root.dataset.moderationHideTrustedMuteCount;
}
if (this.root.dataset.moderationHideTrustedReportCount) {
delete this.root.dataset.moderationHideTrustedReportCount;
}
}
applyModerationContextDatasets(context, {
root: this.root,
thumbnail: this.thumbnailEl,
avatar: this.authorPicEl,
shouldMaskNsfwForOwner: this.shouldMaskNsfwForOwner,
});
}
applyPlaybackDatasets() {
+329
View File
@@ -0,0 +1,329 @@
export function normalizeVideoModerationContext(moderationInput) {
const moderation =
moderationInput && typeof moderationInput === "object"
? moderationInput
: null;
const summary =
moderation?.summary && typeof moderation.summary === "object"
? moderation.summary
: null;
let reportType = "";
if (typeof moderation?.reportType === "string" && moderation.reportType.trim()) {
reportType = moderation.reportType.trim().toLowerCase();
}
if (!reportType && summary && summary.types && typeof summary.types === "object") {
for (const [type, stats] of Object.entries(summary.types)) {
if (stats && Number.isFinite(stats.trusted) && Math.floor(stats.trusted) > 0) {
reportType = String(type).toLowerCase();
break;
}
}
}
let trustedCount = Number.isFinite(moderation?.trustedCount)
? Math.max(0, Math.floor(moderation.trustedCount))
: 0;
if (!trustedCount && summary && summary.types && typeof summary.types === "object") {
for (const stats of Object.values(summary.types)) {
if (stats && Number.isFinite(stats.trusted)) {
trustedCount = Math.max(trustedCount, Math.floor(stats.trusted));
}
}
}
const reporterDisplayNames = Array.isArray(moderation?.reporterDisplayNames)
? moderation.reporterDisplayNames
.map((name) => (typeof name === "string" ? name.trim() : ""))
.filter(Boolean)
: [];
const trustedMuted = moderation?.trustedMuted === true;
let trustedMuteCount = Number.isFinite(moderation?.trustedMuteCount)
? Math.max(0, Math.floor(moderation.trustedMuteCount))
: 0;
if (!trustedMuteCount && Array.isArray(moderation?.trustedMuters)) {
const muters = moderation.trustedMuters
.map((value) => (typeof value === "string" ? value.trim() : ""))
.filter(Boolean);
trustedMuteCount = muters.length;
}
const trustedMuteDisplayNames = Array.isArray(moderation?.trustedMuterDisplayNames)
? moderation.trustedMuterDisplayNames
.map((name) => (typeof name === "string" ? name.trim() : ""))
.filter(Boolean)
: [];
const original =
moderation?.original && typeof moderation.original === "object"
? moderation.original
: {};
const activeHidden = moderation?.hidden === true;
const originalHidden = original.hidden === true;
const hideReasonActive =
typeof moderation?.hideReason === "string" ? moderation.hideReason.trim() : "";
const hideBypassActive =
typeof moderation?.hideBypass === "string" ? moderation.hideBypass.trim() : "";
const originalHideReason =
typeof original.hideReason === "string" ? original.hideReason.trim() : "";
const originalHideBypass =
typeof original.hideBypass === "string" ? original.hideBypass.trim() : "";
const originalHideTriggered = original.hideTriggered === true;
const normalizeHideCounts = (input) => {
if (!input || typeof input !== "object") {
return null;
}
const normalized = {};
let hasValue = false;
if (Number.isFinite(input.trustedMuteCount)) {
normalized.trustedMuteCount = Math.max(0, Math.floor(input.trustedMuteCount));
hasValue = true;
}
if (Number.isFinite(input.trustedReportCount)) {
normalized.trustedReportCount = Math.max(0, Math.floor(input.trustedReportCount));
hasValue = true;
}
return hasValue ? normalized : null;
};
const baseHideCounts = {
trustedMuteCount,
trustedReportCount: trustedCount,
};
const activeHideCounts =
normalizeHideCounts(moderation?.hideCounts) ||
(activeHidden || hideReasonActive ? { ...baseHideCounts } : null);
const originalHideCounts =
normalizeHideCounts(original?.hideCounts) ||
(originalHideTriggered ? { ...baseHideCounts } : null);
const effectiveHideReason = hideReasonActive || originalHideReason;
const effectiveHideCounts = activeHideCounts || originalHideCounts;
const blurReason =
typeof moderation?.blurReason === "string" ? moderation.blurReason.trim() : "";
const originalBlurReason =
typeof original?.blurReason === "string" ? original.blurReason.trim() : "";
const context = {
reportType,
friendlyType: reportType ? reportType.replace(/[_-]+/g, " ").trim() : "",
trustedCount,
reporterDisplayNames,
trustedMuted,
trustedMuteCount,
trustedMuteDisplayNames,
blurReason,
originalBlurReason,
originalBlur: original.blurThumbnail === true,
originalBlockAutoplay: original.blockAutoplay === true,
activeBlur: moderation?.blurThumbnail === true,
activeBlockAutoplay: moderation?.blockAutoplay === true,
overrideActive: moderation?.viewerOverride?.showAnyway === true,
activeHidden,
originalHidden,
originalHideTriggered,
hideReason: hideReasonActive,
hideCounts: activeHideCounts,
hideBypass: hideBypassActive,
originalHideReason,
originalHideCounts,
originalHideBypass,
effectiveHideReason,
effectiveHideCounts,
};
context.shouldShow =
context.originalBlur ||
context.originalBlockAutoplay ||
context.trustedCount > 0 ||
context.trustedMuted ||
context.overrideActive ||
context.originalHidden ||
context.activeHidden ||
context.originalHideTriggered;
context.allowOverride =
context.originalBlur || context.originalBlockAutoplay || context.originalHidden;
return context;
}
function collectElements(...inputs) {
const result = [];
const seen = new Set();
inputs.forEach((input) => {
if (!input) {
return;
}
const values = Array.isArray(input) ? input : [input];
values.forEach((value) => {
if (!value || typeof value !== "object") {
return;
}
if (!("dataset" in value)) {
return;
}
if (seen.has(value)) {
return;
}
seen.add(value);
result.push(value);
});
});
return result;
}
export function applyModerationContextDatasets(
context,
{
root,
roots,
thumbnail,
thumbnails,
avatar,
avatars,
shouldMaskNsfwForOwner = false,
} = {},
) {
if (!context || typeof context !== "object") {
return;
}
const rootElements = collectElements(root, roots);
const thumbnailElements = shouldMaskNsfwForOwner
? []
: collectElements(thumbnail, thumbnails);
const avatarElements = shouldMaskNsfwForOwner ? [] : collectElements(avatar, avatars);
const shouldBlur = context.activeBlur && !context.overrideActive;
thumbnailElements.forEach((el) => {
if (shouldBlur) {
el.dataset.thumbnailState = "blurred";
} else if (el.dataset.thumbnailState === "blurred") {
delete el.dataset.thumbnailState;
}
});
avatarElements.forEach((el) => {
if (shouldBlur) {
el.dataset.visualState = "blurred";
} else if (el.dataset.visualState) {
delete el.dataset.visualState;
}
});
const applyToRoots = (callback) => {
rootElements.forEach((el) => {
if (el) {
callback(el);
}
});
};
applyToRoots((el) => {
if (context.originalBlockAutoplay && !context.overrideActive) {
el.dataset.autoplayPolicy = "blocked";
} else if (el.dataset.autoplayPolicy) {
delete el.dataset.autoplayPolicy;
}
if (context.overrideActive) {
el.dataset.moderationOverride = "show-anyway";
} else if (el.dataset.moderationOverride) {
delete el.dataset.moderationOverride;
}
});
const reportCount = Math.max(0, Number(context.trustedCount) || 0);
applyToRoots((el) => {
if (reportCount > 0) {
el.dataset.moderationReportCount = String(reportCount);
if (context.reportType) {
el.dataset.moderationReportType = context.reportType;
} else if (el.dataset.moderationReportType) {
delete el.dataset.moderationReportType;
}
} else {
if (el.dataset.moderationReportType) {
delete el.dataset.moderationReportType;
}
if (el.dataset.moderationReportCount) {
delete el.dataset.moderationReportCount;
}
}
});
applyToRoots((el) => {
if (context.trustedMuted) {
el.dataset.moderationTrustedMute = "true";
const muteCount = Math.max(0, Number(context.trustedMuteCount) || 0);
if (muteCount > 0) {
el.dataset.moderationTrustedMuteCount = String(muteCount);
} else if (el.dataset.moderationTrustedMuteCount) {
delete el.dataset.moderationTrustedMuteCount;
}
} else {
if (el.dataset.moderationTrustedMute) {
delete el.dataset.moderationTrustedMute;
}
if (el.dataset.moderationTrustedMuteCount) {
delete el.dataset.moderationTrustedMuteCount;
}
}
});
applyToRoots((el) => {
if (context.activeHidden && !context.overrideActive) {
el.dataset.moderationHidden = "true";
const reason = context.effectiveHideReason || context.hideReason;
if (reason) {
el.dataset.moderationHideReason = reason;
} else if (el.dataset.moderationHideReason) {
delete el.dataset.moderationHideReason;
}
const counts = context.hideCounts || context.effectiveHideCounts || null;
const muteCount = counts && Number.isFinite(counts.trustedMuteCount)
? Math.max(0, Number(counts.trustedMuteCount))
: null;
const reportCountHide = counts && Number.isFinite(counts.trustedReportCount)
? Math.max(0, Number(counts.trustedReportCount))
: null;
if (muteCount !== null) {
el.dataset.moderationHideTrustedMuteCount = String(muteCount);
} else if (el.dataset.moderationHideTrustedMuteCount) {
delete el.dataset.moderationHideTrustedMuteCount;
}
if (reportCountHide !== null) {
el.dataset.moderationHideTrustedReportCount = String(reportCountHide);
} else if (el.dataset.moderationHideTrustedReportCount) {
delete el.dataset.moderationHideTrustedReportCount;
}
} else {
if (el.dataset.moderationHidden) {
delete el.dataset.moderationHidden;
}
if (el.dataset.moderationHideReason) {
delete el.dataset.moderationHideReason;
}
if (el.dataset.moderationHideTrustedMuteCount) {
delete el.dataset.moderationHideTrustedMuteCount;
}
if (el.dataset.moderationHideTrustedReportCount) {
delete el.dataset.moderationHideTrustedReportCount;
}
}
});
}
+70 -7
View File
@@ -9,6 +9,10 @@ import {
withMockedNostrTools,
createModerationAppHarness,
} from "../helpers/moderation-test-helpers.mjs";
import {
applyModerationContextDatasets,
normalizeVideoModerationContext,
} from "../../js/ui/moderationUiHelpers.js";
function setupDom(t) {
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", {
@@ -159,7 +163,9 @@ test("VideoCard renders moderation badges and respects viewer override", async (
card.moderationActionButton.click();
await new Promise((resolve) => setTimeout(resolve, 0));
const contextAfterOverride = card.getModerationContext();
const contextAfterOverride = normalizeVideoModerationContext(
card.video?.moderation,
);
assert.equal(contextAfterOverride.overrideActive, true);
assert.equal(contextAfterOverride.activeBlockAutoplay, false);
@@ -223,7 +229,9 @@ test(
document.body.appendChild(card.getRoot());
const contextBeforeOverride = card.getModerationContext();
const contextBeforeOverride = normalizeVideoModerationContext(
card.video?.moderation,
);
assert.equal(contextBeforeOverride.activeHidden, true);
assert.equal(contextBeforeOverride.effectiveHideReason, "trusted-report-hide");
assert.equal(contextBeforeOverride.allowOverride, true);
@@ -243,7 +251,9 @@ test(
card.moderationActionButton.click();
await new Promise((resolve) => setTimeout(resolve, 0));
const contextAfterOverride = card.getModerationContext();
const contextAfterOverride = normalizeVideoModerationContext(
card.video?.moderation,
);
assert.equal(contextAfterOverride.overrideActive, true);
assert.equal(contextAfterOverride.activeHidden, false);
assert.equal(contextAfterOverride.effectiveHideReason, "trusted-report-hide");
@@ -305,7 +315,10 @@ test("VideoCard blurs thumbnails when trusted mute triggers without reports", as
assert.equal(video.moderation.blurThumbnail, true);
assert.equal(video.moderation.blurReason, "trusted-mute");
assert.equal(card.getModerationContext().activeBlur, true);
assert.equal(
normalizeVideoModerationContext(card.video?.moderation).activeBlur,
true,
);
assert.equal(card.thumbnailEl.dataset.thumbnailState, "blurred");
assert.equal(card.authorPicEl.dataset.visualState, "blurred");
assert.equal(card.getRoot().dataset.autoplayPolicy, "blocked");
@@ -364,7 +377,10 @@ test("VideoCard hide action restores trusted mute hide state after override", as
document.body.appendChild(card.getRoot());
assert.equal(card.getModerationContext().activeHidden, true);
assert.equal(
normalizeVideoModerationContext(card.video?.moderation).activeHidden,
true,
);
assert.equal(card.getRoot().dataset.moderationHidden, "true");
card.onModerationOverride = ({ video: overrideVideo, card: overrideCard }) =>
@@ -373,7 +389,9 @@ test("VideoCard hide action restores trusted mute hide state after override", as
card.moderationActionButton.click();
await new Promise((resolve) => setTimeout(resolve, 0));
const contextAfterOverride = card.getModerationContext();
const contextAfterOverride = normalizeVideoModerationContext(
card.video?.moderation,
);
assert.equal(contextAfterOverride.overrideActive, true);
assert.equal(contextAfterOverride.activeHidden, false);
assert.ok(card.moderationHideButton);
@@ -384,9 +402,54 @@ test("VideoCard hide action restores trusted mute hide state after override", as
card.moderationHideButton.click();
await new Promise((resolve) => setTimeout(resolve, 0));
const contextAfterHide = card.getModerationContext();
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.moderationHideButton, 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);
});