Files
bitvid/tests/more-menu-controller.test.mjs
T

270 lines
7.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import MoreMenuController from '../js/ui/moreMenuController.js';
test('copy-link action writes to clipboard and shows success', async () => {
let copied = null;
const clipboard = {
writeText: async (text) => {
copied = text;
},
};
const messages = { error: [], success: [] };
const controller = new MoreMenuController({
clipboard,
callbacks: {
buildShareUrlFromEventId: (eventId) => `https://example.com/watch/${eventId}`,
showError: (message) => messages.error.push(message),
showSuccess: (message) => messages.success.push(message),
getCurrentVideo: () => ({ id: 'fallback-id' }),
},
});
await controller.handleMoreMenuAction('copy-link', { eventId: 'abc123' });
await Promise.resolve();
assert.equal(copied, 'https://example.com/watch/abc123');
assert.deepEqual(messages.error, []);
assert.deepEqual(messages.success, ['Video link copied to clipboard!']);
});
test('blacklist-author requires moderator access and refreshes subscriptions', async () => {
const calls = [];
const accessControl = {
ensureReady: async () => {
calls.push('ensureReady');
},
canEditAdminLists: (actorNpub) => {
calls.push(['canEdit', actorNpub]);
return true;
},
addToBlacklist: async (actorNpub, targetNpub) => {
calls.push(['add', actorNpub, targetNpub]);
return { ok: true };
},
};
let refreshedWith = null;
const subscriptions = {
refreshActiveFeed: async (args) => {
refreshedWith = args;
},
};
const controller = new MoreMenuController({
accessControl,
subscriptions,
callbacks: {
getCurrentUserNpub: () => 'npub1actor',
getCurrentVideo: () => ({ pubkey: 'authorhex' }),
safeEncodeNpub: (pubkey) => `npub:${pubkey}`,
showError: (message) => {
throw new Error(`Unexpected error: ${message}`);
},
showSuccess: (message) => {
calls.push(['success', message]);
},
canCurrentUserManageBlacklist: () => true,
},
});
await controller.handleMoreMenuAction('blacklist-author', {
author: 'authorhex',
});
assert.deepEqual(calls, [
'ensureReady',
['canEdit', 'npub1actor'],
['add', 'npub1actor', 'npub:authorhex'],
['success', 'Creator added to the blacklist.'],
]);
assert.deepEqual(refreshedWith, { reason: 'admin-blacklist-update' });
});
test('blacklist-author shows error when no moderator session is available', async () => {
const errors = [];
const controller = new MoreMenuController({
callbacks: {
getCurrentUserNpub: () => null,
showError: (message) => errors.push(message),
getCurrentVideo: () => ({ pubkey: 'authorhex' }),
},
});
await controller.handleMoreMenuAction('blacklist-author', {
author: 'authorhex',
});
assert.deepEqual(errors, [
'Please login as a moderator to manage the blacklist.',
]);
});
test('block-author updates user blocks, reloads videos, and refreshes feeds', async () => {
const events = [];
const userBlocks = {
ensureLoaded: async (pubkey) => {
events.push(['ensureLoaded', pubkey]);
},
isBlocked: (pubkey) => {
events.push(['isBlocked', pubkey]);
return false;
},
addBlock: async (target, actor) => {
events.push(['addBlock', target, actor]);
},
};
const subscriptions = {
refreshActiveFeed: async (args) => {
events.push(['refreshActiveFeed', args]);
},
};
const moderationStub = {
awaitUserBlockRefresh: async () => {
events.push(['awaitUserBlockRefresh']);
},
};
const controller = new MoreMenuController({
userBlocks,
subscriptions,
moderationService: moderationStub,
callbacks: {
getCurrentUserPubkey: () => 'actorhex',
getCurrentVideo: () => ({ pubkey: 'fallbackhex' }),
safeDecodeNpub: (npub) => (npub === 'npub1target' ? 'targethex' : ''),
showError: (message) => {
throw new Error(`Unexpected error: ${message}`);
},
showSuccess: (message) => {
events.push(['success', message]);
},
loadVideos: async () => {
events.push(['loadVideos']);
},
onUserBlocksUpdated: () => {
events.push(['refreshBlockedList']);
},
},
});
await controller.handleMoreMenuAction('block-author', {
author: 'npub1target',
});
assert.deepEqual(events, [
['ensureLoaded', 'actorhex'],
['isBlocked', 'targethex'],
['addBlock', 'targethex', 'actorhex'],
['success', "Creator blocked. You won't see their videos anymore."],
['refreshBlockedList'],
['awaitUserBlockRefresh'],
['loadVideos'],
['refreshActiveFeed', { reason: 'user-block-update' }],
]);
});
test('mute-author updates viewer mute list and refreshes feeds', async () => {
const events = [];
const moderationStub = {
addAuthorToViewerMuteList: async (target) => {
events.push(['addMute', target]);
return { ok: true };
},
removeAuthorFromViewerMuteList: async () => {
throw new Error('unreachable');
},
isAuthorMutedByViewer: () => false,
};
const subscriptions = {
refreshActiveFeed: async (args) => {
events.push(['refreshActiveFeed', args]);
},
};
const controller = new MoreMenuController({
moderationService: moderationStub,
subscriptions,
callbacks: {
getCurrentUserPubkey: () => 'viewerhex',
getCurrentVideo: () => ({ pubkey: 'fallbackhex' }),
loadVideos: async () => {
events.push(['loadVideos']);
},
showSuccess: (message) => {
events.push(['success', message]);
},
showError: (message) => {
events.push(['error', message]);
},
safeDecodeNpub: () => '',
},
});
const targetHex = 'd'.repeat(64);
await controller.handleMoreMenuAction('mute-author', { author: targetHex });
assert.deepEqual(events, [
['addMute', targetHex],
['success', 'Creator muted. Their videos will be downranked in your feeds.'],
['loadVideos'],
['refreshActiveFeed', { reason: 'viewer-mute-update' }],
]);
});
test('unmute-author removes creators from viewer mute list', async () => {
const events = [];
const moderationStub = {
addAuthorToViewerMuteList: async () => {
throw new Error('unreachable');
},
removeAuthorFromViewerMuteList: async (target) => {
events.push(['removeMute', target]);
return { ok: true };
},
isAuthorMutedByViewer: () => true,
};
const subscriptions = {
refreshActiveFeed: async (args) => {
events.push(['refreshActiveFeed', args]);
},
};
const controller = new MoreMenuController({
moderationService: moderationStub,
subscriptions,
callbacks: {
getCurrentUserPubkey: () => 'viewerhex',
getCurrentVideo: () => ({ pubkey: 'fallbackhex' }),
loadVideos: async () => {
events.push(['loadVideos']);
},
showSuccess: (message) => {
events.push(['success', message]);
},
showError: (message) => {
events.push(['error', message]);
},
safeDecodeNpub: () => '',
},
});
const targetHex = 'e'.repeat(64);
await controller.handleMoreMenuAction('unmute-author', { author: targetHex });
assert.deepEqual(events, [
['removeMute', targetHex],
['success', 'Creator removed from your mute list.'],
['loadVideos'],
['refreshActiveFeed', { reason: 'viewer-mute-update' }],
]);
});