diff --git a/tests/unit/comment-avatar.test.mjs b/tests/unit/comment-avatar.test.mjs new file mode 100644 index 00000000..6ff592c9 --- /dev/null +++ b/tests/unit/comment-avatar.test.mjs @@ -0,0 +1,176 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + normalizeCommentAvatarKey, + resolveCommentAvatarAsset, + registerCommentAvatarFailure, +} from "../../js/ui/components/video-modal/utils/commentAvatar.js"; + +test("normalizeCommentAvatarKey handles inputs correctly", () => { + // Non-string inputs + assert.equal(normalizeCommentAvatarKey(null), ""); + assert.equal(normalizeCommentAvatarKey(undefined), ""); + assert.equal(normalizeCommentAvatarKey(123), ""); + assert.equal(normalizeCommentAvatarKey({}), ""); + + // Whitespace + assert.equal(normalizeCommentAvatarKey(" ABC "), "abc"); + assert.equal(normalizeCommentAvatarKey("\tXYZ\n"), "xyz"); + + // Empty string + assert.equal(normalizeCommentAvatarKey(""), ""); + assert.equal(normalizeCommentAvatarKey(" "), ""); + + // Lowercase + assert.equal(normalizeCommentAvatarKey("HeLLo"), "hello"); +}); + +test("resolveCommentAvatarAsset resolves avatars correctly", () => { + const defaultAvatar = "default.png"; + const pubkey = "pk1"; + const sanitizedPicture = "https://example.com/avatar.png"; + + // Scenario 1: Basic resolution (no cache, no failures) + let result = resolveCommentAvatarAsset({ + cache: new Map(), + failures: new Set(), + defaultAvatar, + pubkey, + sanitizedPicture, + }); + assert.deepEqual(result, { url: sanitizedPicture, source: sanitizedPicture }); + + // Scenario 2: Missing picture + result = resolveCommentAvatarAsset({ + cache: new Map(), + failures: new Set(), + defaultAvatar, + pubkey, + sanitizedPicture: null, + }); + assert.deepEqual(result, { url: defaultAvatar, source: "" }); + + // Scenario 3: Known failure + const failures = new Set([sanitizedPicture]); + result = resolveCommentAvatarAsset({ + cache: new Map(), + failures, + defaultAvatar, + pubkey, + sanitizedPicture, + }); + assert.deepEqual(result, { url: defaultAvatar, source: "" }); + + // Scenario 4: Cache hit (matching source) + const cache = new Map(); + cache.set(pubkey, { url: sanitizedPicture, source: sanitizedPicture }); + result = resolveCommentAvatarAsset({ + cache, + failures: new Set(), + defaultAvatar, + pubkey, + sanitizedPicture, + }); + assert.deepEqual(result, { url: sanitizedPicture, source: sanitizedPicture }); + + // Scenario 5: Cache hit (source mismatch - new avatar) + const newPicture = "https://example.com/new.png"; + result = resolveCommentAvatarAsset({ + cache, // cache has old picture + failures: new Set(), + defaultAvatar, + pubkey, + sanitizedPicture: newPicture, + }); + assert.deepEqual(result, { url: newPicture, source: newPicture }); + // Verify cache update + assert.deepEqual(cache.get(pubkey), { url: newPicture, source: newPicture }); + + // Scenario 6: Cache hit (source mismatch - removed avatar) + // Note: Implementation preserves cached avatar if input is empty (persistence) + result = resolveCommentAvatarAsset({ + cache, // cache has new picture + failures: new Set(), + defaultAvatar, + pubkey, + sanitizedPicture: "", // avatar removed or missing + }); + assert.deepEqual(result, { url: newPicture, source: newPicture }); + // Verify cache remains unchanged + assert.deepEqual(cache.get(pubkey), { url: newPicture, source: newPicture }); + + // Scenario 7: Cache hit (cached failure) + cache.set(pubkey, { url: defaultAvatar, source: "" }); + result = resolveCommentAvatarAsset({ + cache, + failures: new Set(), + defaultAvatar, + pubkey, + sanitizedPicture: "", // no new picture provided + }); + assert.deepEqual(result, { url: defaultAvatar, source: "" }); +}); + +test("registerCommentAvatarFailure registers failures correctly", () => { + const defaultAvatar = "default.png"; + const sourceUrl = "https://example.com/bad.png"; + + // Scenario 1: Basic failure registration + const failures = new Set(); + const cache = new Map(); + registerCommentAvatarFailure({ + cache, + failures, + defaultAvatar, + sourceUrl, + }); + assert.ok(failures.has(sourceUrl)); + + // Scenario 2: Cache invalidation on failure + cache.set("pk1", { url: sourceUrl, source: sourceUrl }); + cache.set("pk2", { + url: "https://example.com/good.png", + source: "https://example.com/good.png", + }); + + registerCommentAvatarFailure({ + cache, + failures, + defaultAvatar, + sourceUrl, + }); + + // Should update pk1 to default + assert.deepEqual(cache.get("pk1"), { url: defaultAvatar, source: "" }); + // Should keep pk2 + assert.deepEqual(cache.get("pk2"), { + url: "https://example.com/good.png", + source: "https://example.com/good.png", + }); + + // Scenario 3: Ignore invalid inputs + const failures2 = new Set(); + registerCommentAvatarFailure({ + cache: new Map(), + failures: failures2, + defaultAvatar, + sourceUrl: "", + }); + assert.equal(failures2.size, 0); + + registerCommentAvatarFailure({ + cache: new Map(), + failures: failures2, + defaultAvatar, + sourceUrl: defaultAvatar, + }); + assert.equal(failures2.size, 0); + + registerCommentAvatarFailure({ + cache: new Map(), + failures: failures2, + defaultAvatar, + sourceUrl: null, + }); + assert.equal(failures2.size, 0); +});