diff --git a/package.json b/package.json index ed35ab84..386a6506 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.20.105", + "version": "0.20.106", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/css/style.css b/src/css/style.css index 2b332dfd..7fb01378 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -10211,6 +10211,16 @@ moderation-warning .toggle-content { line-height: 1.4; } +.label-preference-disabled-note { + display: flex; + align-items: center; + gap: 4px; + margin-top: 4px; + color: var(--text-color-muted); + font-size: 14px; + font-style: italic; +} + .label-preference-name + .label-preference-buttons { margin-top: 8px; } diff --git a/src/js/components/moderation-warning.js b/src/js/components/moderation-warning.js index 6518b9c0..cb94c5c0 100644 --- a/src/js/components/moderation-warning.js +++ b/src/js/components/moderation-warning.js @@ -11,6 +11,7 @@ class ModerationWarning extends Component { this.labelerLink = this.getAttribute("labelerLink"); this.labelerName = this.getAttribute("labelerName"); this.iconStyle = this.getAttribute("icon-style") ?? "info"; + this.noOverride = this.hasAttribute("no-override"); this.expanded = false; this._children = getChildrenFragment(this); this.innerHTML = ""; @@ -23,30 +24,33 @@ class ModerationWarning extends Component { this.iconStyle === "closed-eye" ? html`` : html``; + const labelTemplate = html` + ${iconHtml} + ${this.label} + `; render( html` -
this.toggle()} - @keydown=${(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - e.stopPropagation(); - this.toggle(); - } - }} - > - - ${iconHtml} - ${this.label} - - -
+ ${this.noOverride + ? html`
${labelTemplate}
` + : html`
this.toggle()} + @keydown=${(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + e.stopPropagation(); + this.toggle(); + } + }} + > + ${labelTemplate} + +
`}
${this.labelerName ? html`
getBlurEntryRank(currentLabel) + ) { + currentLabel = entry; } } return currentLabel; @@ -510,12 +545,20 @@ export class Preferences { continue; } const visibility = this.getLabelVisibility(label, labelDefinition); - if (visibility === "ignore") continue; - const entry = { visibility, label, labelDefinition, labeler }; - if (visibility === "hide") { + if (visibility !== "hide" && visibility !== "warn") continue; + const entry = this._createBlurLabelEntry( + visibility, + label, + labelDefinition, + labeler, + ); + if (entry.noOverride) { return entry; } - if (visibility === "warn" && !currentLabel) { + if ( + !currentLabel || + getBlurEntryRank(entry) > getBlurEntryRank(currentLabel) + ) { currentLabel = entry; } } @@ -842,6 +885,13 @@ export class Preferences { ); } + static getAdultContentPreference(obj) { + return Preferences.getPreferenceByType( + obj, + "app.bsky.actor.defs#adultContentPref", + ); + } + static getContentLabelPreferences(obj) { return obj.filter( (pref) => pref.$type === "app.bsky.actor.defs#contentLabelPref", diff --git a/src/js/templates/labelerSettings.template.js b/src/js/templates/labelerSettings.template.js index a42d95a7..479b9793 100644 --- a/src/js/templates/labelerSettings.template.js +++ b/src/js/templates/labelerSettings.template.js @@ -5,6 +5,7 @@ import { getDefaultLabelSetting, } from "/js/dataHelpers.js"; import { classnames, noop } from "/js/utils.js"; +import "/js/components/app-icon.js"; function doShowWarnButton(labelDefinition) { return !( @@ -23,6 +24,7 @@ function labelPreferenceRowTemplate({ labelDefinition, value, isSubscribed, + isAdultDisabled, onClick, }) { const { name, description } = getLabelNameAndDescription(labelDefinition); @@ -35,6 +37,15 @@ function labelPreferenceRowTemplate({ ${description ? html`
${description}
` : null} + ${isAdultDisabled + ? html`
+ + Adult content is disabled. +
` + : null} ${isSubscribed ? html`
onClick("ignore")} > @@ -58,6 +70,7 @@ function labelPreferenceRowTemplate({ active: value === "warn", })} data-testid="label-pref-button" + ?disabled=${isAdultDisabled} data-teststate="warn" @click=${() => onClick("warn")} > @@ -70,6 +83,7 @@ function labelPreferenceRowTemplate({ active: value === "hide", })} data-testid="label-pref-button" + ?disabled=${isAdultDisabled} data-teststate="hide" @click=${() => onClick("hide")} > @@ -86,6 +100,7 @@ export function labelerSettingsTemplate({ labelerInfo, isSubscribed, labelerSettings, + isAdultContentEnabled, onClick = noop, }) { const labelValueDefinitions = @@ -123,13 +138,19 @@ export function labelerSettingsTemplate({ const currentSetting = labelerSettings.find( (pref) => pref.label === labelDefinition.identifier, ); - const value = currentSetting + const isAdultDisabled = + !!labelDefinition.adultOnly && !isAdultContentEnabled; + let value = currentSetting ? currentSetting.visibility : getDefaultLabelSetting(labelDefinition); + if (isAdultDisabled) { + value = "hide"; + } return labelPreferenceRowTemplate({ labelDefinition, value, isSubscribed, + isAdultDisabled, onClick: (newValue) => { if (newValue !== value) { onClick(labelDefinition.identifier, newValue); diff --git a/src/js/templates/largePost.template.js b/src/js/templates/largePost.template.js index 588660eb..2b886139 100644 --- a/src/js/templates/largePost.template.js +++ b/src/js/templates/largePost.template.js @@ -24,6 +24,7 @@ import { authorBadgesTemplate } from "/js/templates/labelBadges.template.js"; import { blockedPostTemplate } from "/js/templates/blockedPost.template.js"; import { whoCanReplyBadgeTemplate } from "/js/templates/whoCanReplyBadge.template.js"; import { postNumberBadgeTemplate } from "/js/templates/postNumberBadge.template.js"; +import { moderationWarningTemplate } from "/js/templates/moderationWarning.template.js"; import { WhoCanReplyModal } from "/js/modals/whoCanReply.modal.js"; import { notFoundPostTemplate } from "/js/templates/notFoundPost.template.js"; import { unavailablePostTemplate } from "/js/templates/unavailablePost.template.js"; @@ -53,6 +54,20 @@ function mutedWarningTemplate({ post, children }) { return children; } +function lockedContentWarningTemplate({ post, lockedContentLabel, children }) { + if (!lockedContentLabel) { + return children; + } + return moderationWarningTemplate({ + className: "post-content-warning", + labelDefinition: lockedContentLabel.labelDefinition, + labeler: lockedContentLabel.labeler, + noOverride: true, + isAuthorLabel: lockedContentLabel.label.uri === post.author?.did, + children, + }); +} + function followButtonTemplate({ profile, isFollowPending, onClick }) { const isFollowing = !!profile.viewer?.following; const isFollowedBy = !!profile.viewer?.followedBy; @@ -173,8 +188,12 @@ export function largePostTemplate({ const canEditInteractionSettings = isUserPost && isRootPost; const badgeLabels = post.badgeLabels ?? []; const contentLabel = post.contentLabel; + const isContentLabelVisible = + contentLabel && contentLabel.visibility !== "ignore"; + const lockedContentLabel = + isContentLabelVisible && contentLabel.noOverride ? contentLabel : null; // Instead of hiding, add the content label to the badge labels - if (contentLabel && contentLabel.visibility !== "ignore") { + if (isContentLabelVisible && !lockedContentLabel) { badgeLabels.push(contentLabel); } return html`
@@ -209,44 +228,48 @@ export function largePostTemplate({
${mutedWarningTemplate({ post, - children: html`
- ${postText.length > 0 - ? html`
- -
` - : postNumbering - ? postNumberBadgeTemplate({ - numbering: postNumbering, - inline: false, - }) - : ""} - ${embed - ? html`
- ${postEmbedTemplate({ - embed, - mediaLabel: post.mediaLabel, - isAuthenticated, - pluginService, - })} -
` - : null} -
`, + children: lockedContentWarningTemplate({ + post, + lockedContentLabel, + children: html`
+ ${postText.length > 0 + ? html`
+ +
` + : postNumbering + ? postNumberBadgeTemplate({ + numbering: postNumbering, + inline: false, + }) + : ""} + ${embed + ? html`
+ ${postEmbedTemplate({ + embed, + mediaLabel: post.mediaLabel, + isAuthenticated, + pluginService, + })} +
` + : null} +
`, + }), })}
${formatFullTimestamp(post.indexedAt)} diff --git a/src/js/templates/moderationWarning.template.js b/src/js/templates/moderationWarning.template.js index 38867db6..2082505c 100644 --- a/src/js/templates/moderationWarning.template.js +++ b/src/js/templates/moderationWarning.template.js @@ -8,6 +8,7 @@ export function moderationWarningTemplate({ isAuthorLabel, labelDefinition, labeler, + noOverride, children, }) { let { name: labelName } = getLabelNameAndDescription(labelDefinition); @@ -20,6 +21,7 @@ export function moderationWarningTemplate({ return html` { const clickedBar = !!e.target.closest(".top-bar"); if (clickedBar) { diff --git a/src/js/templates/postEmbed.template.js b/src/js/templates/postEmbed.template.js index 53122448..6279993f 100644 --- a/src/js/templates/postEmbed.template.js +++ b/src/js/templates/postEmbed.template.js @@ -39,6 +39,7 @@ function moderationWarningWrapperTemplate({ children, mediaLabel }) { ? moderationWarningTemplate({ labelDefinition: mediaLabel.labelDefinition, labeler: mediaLabel.labeler, + noOverride: mediaLabel.noOverride, isAuthorLabel: false, children, }) @@ -72,7 +73,13 @@ function notFoundQuoteTemplate() {
`; } -function mutedWrapperTemplate({ isMuted, label, iconStyle, children }) { +function mutedWrapperTemplate({ + isMuted, + label, + iconStyle, + noOverride, + children, +}) { if (isMuted) { return html` { @@ -85,6 +92,7 @@ function mutedWrapperTemplate({ isMuted, label, iconStyle, children }) { class="quoted-account-muted-warning" label=${label} icon-style=${iconStyle} + ?no-override=${noOverride} >${children}`; } @@ -169,6 +177,7 @@ export function quotedPostTemplate({ let isMuted = false; let mutedLabel = null; let mutedIconStyle = "info"; + let mutedNoOverride = false; if (quotedPost.hasMutedWord) { isMuted = true; mutedLabel = "Hidden by muted word"; @@ -194,6 +203,7 @@ export function quotedPostTemplate({ ); mutedLabel = labelName; mutedIconStyle = "info"; + mutedNoOverride = contentLabel.noOverride; const isAuthorLabel = contentLabel.label.uri === quotedPost?.author?.did; if (isAuthorLabel) { mutedLabel += " (Account)"; @@ -213,6 +223,7 @@ export function quotedPostTemplate({ isMuted, label: mutedLabel, iconStyle: mutedIconStyle, + noOverride: mutedNoOverride, children: html`
${avatarTemplate({ diff --git a/src/js/templates/smallPost.template.js b/src/js/templates/smallPost.template.js index 8cc02636..656a66c8 100644 --- a/src/js/templates/smallPost.template.js +++ b/src/js/templates/smallPost.template.js @@ -38,6 +38,7 @@ function contentWarningTemplate({ className: "post-content-warning", labelDefinition: contentLabel.labelDefinition, labeler: contentLabel.labeler, + noOverride: contentLabel.noOverride, isAuthorLabel, children, }); diff --git a/src/js/views/profile.view.js b/src/js/views/profile.view.js index 4f693f00..b49a6679 100644 --- a/src/js/views/profile.view.js +++ b/src/js/views/profile.view.js @@ -301,12 +301,14 @@ export default async function profileView({ let isDefaultLabeler = profile.did === BSKY_LABELER_DID; let isSubscribed = false; let labelerSettings = null; + let isAdultContentEnabled = false; if (isLabeler) { const preferences = dataLayer.derived.$preferences.get(); isSubscribed = isDefaultLabeler ? true : preferences?.isSubscribedToLabeler(profile.did); labelerSettings = dataLayer.derived.$labelerSettings.get(profile.did); + isAdultContentEnabled = preferences?.isAdultContentEnabled() ?? false; } return html`
@@ -409,6 +411,7 @@ export default async function profileView({ profile, isSubscribed, labelerSettings, + isAdultContentEnabled, onClick: (label, visibility) => handleLabelerSettingsClick( profile.did, diff --git a/tests/unit/specs/components/moderation-warning.test.js b/tests/unit/specs/components/moderation-warning.test.js index f6c6b069..180bc223 100644 --- a/tests/unit/specs/components/moderation-warning.test.js +++ b/tests/unit/specs/components/moderation-warning.test.js @@ -277,4 +277,26 @@ describe("moderation-warning", () => { assert.deepEqual(child.textContent, "Original"); }); }); + + describe("ModerationWarning - no override", () => { + it("should not render a show/hide toggle", () => { + const element = document.createElement("moderation-warning"); + element.setAttribute("no-override", ""); + element.setAttribute("label", "Adult Content"); + document.body.appendChild(element); + const topBar = element.querySelector(".top-bar"); + assert(topBar.textContent.includes("Adult Content")); + assert.deepEqual(topBar.getAttribute("role"), null); + assert.deepEqual(element.querySelector(".show-hide-label"), null); + }); + + it("should keep children hidden when the top bar is clicked", () => { + const element = document.createElement("moderation-warning"); + element.setAttribute("no-override", ""); + element.innerHTML = "
Hidden content
"; + document.body.appendChild(element); + element.querySelector(".top-bar").click(); + assert(element.querySelector(".toggle-content").hidden); + }); + }); }); diff --git a/tests/unit/specs/preferences.test.js b/tests/unit/specs/preferences.test.js index e017a6d2..3b5e8603 100644 --- a/tests/unit/specs/preferences.test.js +++ b/tests/unit/specs/preferences.test.js @@ -2647,9 +2647,14 @@ describe("Preferences.getContentLabel - global labels", () => { }); }); +const adultContentEnabledPref = { + $type: "app.bsky.actor.defs#adultContentPref", + enabled: true, +}; + describe("Preferences.getMediaLabel - global self-labels", () => { it("should handle porn self-label with default hide visibility", () => { - const preferences = new Preferences([], []); + const preferences = new Preferences([adultContentEnabledPref], []); // Self-labels have src = author's DID, not a labeler's DID const post = { author: { did: "did:plc:author123" }, @@ -2663,7 +2668,7 @@ describe("Preferences.getMediaLabel - global self-labels", () => { }); it("should handle sexual self-label with default warn visibility", () => { - const preferences = new Preferences([], []); + const preferences = new Preferences([adultContentEnabledPref], []); const post = { author: { did: "did:plc:author123" }, labels: [{ src: "did:plc:author123", val: "sexual" }], @@ -2675,7 +2680,7 @@ describe("Preferences.getMediaLabel - global self-labels", () => { }); it("should handle nudity self-label with default ignore visibility", () => { - const preferences = new Preferences([], []); + const preferences = new Preferences([adultContentEnabledPref], []); const post = { author: { did: "did:plc:author123" }, labels: [{ src: "did:plc:author123", val: "nudity" }], @@ -2687,7 +2692,7 @@ describe("Preferences.getMediaLabel - global self-labels", () => { }); it("should handle graphic-media self-label", () => { - const preferences = new Preferences([], []); + const preferences = new Preferences([adultContentEnabledPref], []); const post = { author: { did: "did:plc:author123" }, labels: [{ src: "did:plc:author123", val: "graphic-media" }], @@ -2699,7 +2704,7 @@ describe("Preferences.getMediaLabel - global self-labels", () => { }); it("should handle legacy gore label", () => { - const preferences = new Preferences([], []); + const preferences = new Preferences([adultContentEnabledPref], []); const post = { author: { did: "did:plc:author123" }, labels: [{ src: "did:plc:author123", val: "gore" }], @@ -2721,7 +2726,7 @@ describe("Preferences.getMediaLabel - global self-labels", () => { }, ]; - const preferences = new Preferences(obj, []); + const preferences = new Preferences([adultContentEnabledPref, ...obj], []); const post = { author: { did: "did:plc:author123" }, labels: [{ src: "did:plc:author123", val: "porn" }], @@ -2732,7 +2737,7 @@ describe("Preferences.getMediaLabel - global self-labels", () => { }); it("should prefer most restrictive self-label", () => { - const preferences = new Preferences([], []); + const preferences = new Preferences([adultContentEnabledPref], []); const post = { author: { did: "did:plc:author123" }, labels: [ @@ -2888,7 +2893,7 @@ describe("Preferences.getMediaLabel - mixed global and custom labels", () => { }, ]; - const preferences = new Preferences([], labelerDefs); + const preferences = new Preferences([adultContentEnabledPref], labelerDefs); const post = { author: { did: "did:plc:author123" }, labels: [ @@ -2920,7 +2925,7 @@ describe("Preferences.getMediaLabel - mixed global and custom labels", () => { }, ]; - const preferences = new Preferences([], labelerDefs); + const preferences = new Preferences([adultContentEnabledPref], labelerDefs); const post = { author: { did: "did:plc:author123" }, labels: [ @@ -2967,7 +2972,10 @@ describe("Preferences.getMediaLabel - mixed global and custom labels", () => { }, ]; - const preferences = new Preferences(obj, labelerDefs); + const preferences = new Preferences( + [adultContentEnabledPref, ...obj], + labelerDefs, + ); const post = { author: { did: "did:plc:author123" }, labels: [ @@ -3588,3 +3596,170 @@ describe("Preferences.setThreadView", () => { assert.deepEqual(newPreferences.getThreadView(), "linear"); }); }); + +describe("Preferences - adult-only labels", () => { + const adultContentPref = (enabled) => ({ + $type: "app.bsky.actor.defs#adultContentPref", + enabled, + }); + const labelerDefs = [ + { + creator: { did: "did:labeler1", handle: "labeler.test" }, + policies: { + labelValueDefinitions: [ + { + identifier: "adult-custom", + blurs: "content", + defaultSetting: "warn", + adultOnly: true, + locales: [{ lang: "en", name: "Adult Custom" }], + }, + ], + }, + }, + ]; + const ignorePref = { + $type: "app.bsky.actor.defs#contentLabelPref", + label: "adult-custom", + labelerDid: "did:labeler1", + visibility: "ignore", + }; + const labeledPost = { + author: { did: "did:plc:author123" }, + labels: [{ src: "did:labeler1", val: "adult-custom" }], + }; + + it("should treat adult content as disabled when no preference exists", () => { + assert.deepEqual(new Preferences([], []).isAdultContentEnabled(), false); + }); + + it("should read the adult content preference", () => { + const preferences = new Preferences([adultContentPref(true)], []); + assert.deepEqual(preferences.isAdultContentEnabled(), true); + }); + + it("should force adult-only labels to hide when adult content is disabled", () => { + const preferences = new Preferences( + [adultContentPref(false), ignorePref], + labelerDefs, + ); + const result = preferences.getContentLabel(labeledPost); + + assert.deepEqual(result.visibility, "hide"); + assert.deepEqual(result.noOverride, true); + }); + + it("should respect the label preference when adult content is enabled", () => { + const preferences = new Preferences( + [adultContentPref(true), ignorePref], + labelerDefs, + ); + + assert.deepEqual(preferences.getContentLabel(labeledPost), null); + }); + + it("should allow overriding adult-only labels when adult content is enabled", () => { + const preferences = new Preferences([adultContentPref(true)], labelerDefs); + const result = preferences.getContentLabel(labeledPost); + + assert.deepEqual(result.visibility, "warn"); + assert.deepEqual(result.noOverride, false); + }); + + it("should force adult-only global self-labels to hide when adult content is disabled", () => { + const preferences = new Preferences([], []); + const post = { + author: { did: "did:plc:author123" }, + labels: [{ src: "did:plc:author123", val: "graphic-media" }], + }; + const result = preferences.getMediaLabel(post); + + assert.deepEqual(result.visibility, "hide"); + assert.deepEqual(result.noOverride, true); + }); + + it("should not affect labels that are not adult-only", () => { + const preferences = new Preferences([], []); + const post = { + author: { did: "did:plc:author123" }, + labels: [{ src: "did:plc:author123", val: "!warn" }], + }; + const result = preferences.getContentLabel(post); + + assert.deepEqual(result.visibility, "warn"); + assert.deepEqual(result.noOverride, false); + }); + + it("should prefer a non-overridable label over an earlier user-set hide", () => { + const hideNudityPref = { + $type: "app.bsky.actor.defs#contentLabelPref", + label: "nudity", + visibility: "hide", + }; + const preferences = new Preferences([hideNudityPref], []); + const post = { + author: { did: "did:plc:author123" }, + labels: [ + { src: "did:plc:author123", val: "nudity" }, + { src: "did:plc:author123", val: "porn" }, + ], + }; + const result = preferences.getMediaLabel(post); + + assert.deepEqual(result.label.val, "porn"); + assert.deepEqual(result.noOverride, true); + }); + + it("should prefer a non-overridable profile blur label over an earlier user-set hide", () => { + const hideNudityPref = { + $type: "app.bsky.actor.defs#contentLabelPref", + label: "nudity", + visibility: "hide", + }; + const preferences = new Preferences([hideNudityPref], []); + const result = preferences.getProfileBlurLabel({ + did: "did:plc:author123", + labels: [ + { src: "did:plc:author123", val: "nudity" }, + { src: "did:plc:author123", val: "porn" }, + ], + }); + + assert.deepEqual(result.label.val, "porn"); + assert.deepEqual(result.noOverride, true); + }); + + it("should prefer a hide over an earlier warn", () => { + const hideNudityPref = { + $type: "app.bsky.actor.defs#contentLabelPref", + label: "nudity", + visibility: "hide", + }; + const preferences = new Preferences( + [adultContentPref(true), hideNudityPref], + [], + ); + const post = { + author: { did: "did:plc:author123" }, + labels: [ + { src: "did:plc:author123", val: "sexual" }, + { src: "did:plc:author123", val: "nudity" }, + ], + }; + const result = preferences.getMediaLabel(post); + + assert.deepEqual(result.label.val, "nudity"); + assert.deepEqual(result.visibility, "hide"); + assert.deepEqual(result.noOverride, false); + }); + + it("should mark adult-only profile blur labels as non-overridable", () => { + const preferences = new Preferences([], labelerDefs); + const result = preferences.getProfileBlurLabel({ + labels: [{ src: "did:labeler1", val: "adult-custom" }], + }); + + assert.deepEqual(result.visibility, "hide"); + assert.deepEqual(result.noOverride, true); + }); +}); diff --git a/tests/unit/specs/templates/labelerSettings.template.test.js b/tests/unit/specs/templates/labelerSettings.template.test.js index c2a30ab2..7ff0cfe4 100644 --- a/tests/unit/specs/templates/labelerSettings.template.test.js +++ b/tests/unit/specs/templates/labelerSettings.template.test.js @@ -242,3 +242,74 @@ describe("labelerSettingsTemplate - current settings", () => { assert.deepEqual(clickedValue, "hide"); }); }); + +describe("labelerSettingsTemplate - adult-only labels", () => { + const adultLabelerInfo = { + ...mockLabelerInfo, + policies: { + labelValueDefinitions: [ + { + identifier: "adult-custom", + blurs: "media", + severity: "none", + defaultSetting: "warn", + adultOnly: true, + locales: [{ lang: "en", name: "Adult Custom" }], + }, + ], + }, + }; + + function renderAdultLabelerSettings({ isAdultContentEnabled }) { + const container = document.createElement("div"); + render( + labelerSettingsTemplate({ + labelerInfo: adultLabelerInfo, + isSubscribed: true, + labelerSettings: [{ label: "adult-custom", visibility: "ignore" }], + isAdultContentEnabled, + }), + container, + ); + return container; + } + + it("should disable the controls and show hide when adult content is disabled", () => { + const container = renderAdultLabelerSettings({ + isAdultContentEnabled: false, + }); + const buttons = [ + ...container.querySelectorAll("[data-testid='label-pref-button']"), + ]; + assert(buttons.every((button) => button.disabled)); + const activeButton = buttons.find((button) => + button.classList.contains("active"), + ); + assert.deepEqual(activeButton.dataset.teststate, "hide"); + assert( + container.querySelector( + "[data-testid='label-preference-adult-disabled']", + ) !== null, + ); + }); + + it("should allow configuring the label when adult content is enabled", () => { + const container = renderAdultLabelerSettings({ + isAdultContentEnabled: true, + }); + const buttons = [ + ...container.querySelectorAll("[data-testid='label-pref-button']"), + ]; + assert(buttons.every((button) => !button.disabled)); + const activeButton = buttons.find((button) => + button.classList.contains("active"), + ); + assert.deepEqual(activeButton.dataset.teststate, "off"); + assert.deepEqual( + container.querySelector( + "[data-testid='label-preference-adult-disabled']", + ), + null, + ); + }); +}); diff --git a/tests/unit/specs/templates/largePost.template.test.js b/tests/unit/specs/templates/largePost.template.test.js index c3edd029..5bfdbdc3 100644 --- a/tests/unit/specs/templates/largePost.template.test.js +++ b/tests/unit/specs/templates/largePost.template.test.js @@ -330,6 +330,56 @@ describe("largePostTemplate - moderation", () => { assert.deepEqual(warning.getAttribute("icon-style"), "closed-eye"); }); + it("should show an adult-only content label as a warning instead of a badge", () => { + const labeledPost = { + ...post, + contentLabel: { + visibility: "warn", + label: { src: "did:plc:labeler", val: "adult-custom", uri: post.uri }, + labelDefinition: { + identifier: "adult-custom", + blurs: "content", + adultOnly: true, + locales: [{ lang: "en", name: "Adult Custom" }], + }, + labeler: null, + noOverride: true, + }, + }; + const result = largePostTemplate({ post: labeledPost, ...baseProps }); + const container = document.createElement("div"); + render(result, container); + const warning = container.querySelector("moderation-warning"); + assert(warning !== null); + assert(warning.hasAttribute("no-override")); + assert(warning.querySelector(".post-body") !== null); + }); + + it("should show an overridable adult-only content label as a badge", () => { + const labeledPost = { + ...post, + contentLabel: { + visibility: "warn", + label: { src: "did:plc:labeler", val: "adult-custom", uri: post.uri }, + labelDefinition: { + identifier: "adult-custom", + blurs: "content", + adultOnly: true, + locales: [{ lang: "en", name: "Adult Custom" }], + }, + labeler: { + creator: { did: "did:plc:labeler", handle: "labeler.test" }, + }, + noOverride: false, + }, + }; + const result = largePostTemplate({ post: labeledPost, ...baseProps }); + const container = document.createElement("div"); + render(result, container); + assert.deepEqual(container.querySelector("moderation-warning"), null); + assert(container.querySelector("[data-testid='label-badge']") !== null); + }); + it("should not show moderation warning for normal post", () => { const normalPost = { ...post, diff --git a/tests/unit/specs/templates/moderationWarning.template.test.js b/tests/unit/specs/templates/moderationWarning.template.test.js index 74bc0c92..b27e8e1e 100644 --- a/tests/unit/specs/templates/moderationWarning.template.test.js +++ b/tests/unit/specs/templates/moderationWarning.template.test.js @@ -138,3 +138,31 @@ describe("moderationWarningTemplate - custom className", () => { assert(warning.classList.contains("custom-warning")); }); }); + +describe("moderationWarningTemplate - noOverride", () => { + it("should set the no-override attribute when noOverride is true", () => { + const result = moderationWarningTemplate({ + labelDefinition: mockLabelDefinition, + labeler: mockLabeler, + noOverride: true, + children: html`
Hidden content
`, + }); + const container = document.createElement("div"); + render(result, container); + const warning = container.querySelector("moderation-warning"); + assert(warning.hasAttribute("no-override")); + }); + + it("should not set the no-override attribute when noOverride is false", () => { + const result = moderationWarningTemplate({ + labelDefinition: mockLabelDefinition, + labeler: mockLabeler, + noOverride: false, + children: html`
Hidden content
`, + }); + const container = document.createElement("div"); + render(result, container); + const warning = container.querySelector("moderation-warning"); + assert(!warning.hasAttribute("no-override")); + }); +});