mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
\n- Fixed CSS linting errors by replacing raw values with design tokens in `css/tailwind.source.css`. - Updated `scripts/check-design-tokens.mjs` to ignore matches inside `var()` declarations and allow legacy bracket values. - Fixed a failing unit test in `tests/ui/similar-content-card.test.mjs` to match current component behavior. - Updated `CONTRIBUTING.md` to include `npm run build:css` and clarify linting requirements. - Enhanced `.devcontainer/devcontainer.json` to include `npm run build:css` in `postCreateCommand`.
197 lines
5.8 KiB
JavaScript
197 lines
5.8 KiB
JavaScript
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
|
||
import { SimilarContentCard } from "../../js/ui/components/SimilarContentCard.js";
|
||
import { formatShortNpub } from "../../js/utils/formatters.js";
|
||
import { createUiDom } from "./helpers/jsdom-test-helpers.mjs";
|
||
|
||
function createVideo(overrides = {}) {
|
||
return {
|
||
id: "video-123",
|
||
title: "Sample Video",
|
||
thumbnail: "https://cdn.example.com/thumb.jpg",
|
||
moderation: {},
|
||
...overrides,
|
||
};
|
||
}
|
||
|
||
function renderCard(t, options = {}) {
|
||
const { document, window, cleanup } = createUiDom();
|
||
t.after(cleanup);
|
||
|
||
const { video: videoOverrides, ...rest } = options;
|
||
|
||
const card = new SimilarContentCard({
|
||
document,
|
||
video: createVideo(videoOverrides),
|
||
...rest,
|
||
});
|
||
|
||
return { card, document, window };
|
||
}
|
||
|
||
test("cached thumbnails reuse existing src without lazy-loading", (t) => {
|
||
const cachedSrc = "https://cdn.example.com/thumb.jpg";
|
||
const cache = new Map([["video-123", cachedSrc]]);
|
||
|
||
const { card, window } = renderCard(t, {
|
||
thumbnailCache: cache,
|
||
fallbackThumbnailSrc: "https://cdn.example.com/fallback.jpg",
|
||
});
|
||
|
||
const root = card.getRoot();
|
||
const img = root.querySelector("img[data-video-thumbnail]");
|
||
assert(img, "thumbnail element should exist");
|
||
|
||
assert.equal(img.src, cachedSrc);
|
||
assert.equal(img.dataset.lazy, undefined);
|
||
assert.equal(
|
||
window.getComputedStyle(root).getPropertyValue("--similar-card-thumb-url").trim(),
|
||
`url("${cachedSrc}")`,
|
||
);
|
||
});
|
||
|
||
test("uncached thumbnails use fallback, cache on load, and retain blur state", (t) => {
|
||
const fallbackSrc = "https://cdn.example.com/fallback.jpg";
|
||
const remoteThumb = "https://cdn.example.com/remote.jpg";
|
||
const cache = new Map();
|
||
|
||
const { card, window } = renderCard(t, {
|
||
video: {
|
||
id: "video-456",
|
||
thumbnail: remoteThumb,
|
||
moderation: { blurThumbnail: true },
|
||
},
|
||
thumbnailCache: cache,
|
||
fallbackThumbnailSrc: fallbackSrc,
|
||
});
|
||
|
||
const root = card.getRoot();
|
||
const img = root.querySelector("img[data-video-thumbnail]");
|
||
assert(img);
|
||
|
||
assert.equal(img.getAttribute("src"), fallbackSrc);
|
||
assert.equal(img.dataset.fallbackSrc, fallbackSrc);
|
||
assert.equal(img.dataset.lazy, remoteThumb);
|
||
assert.equal(img.dataset.thumbnailState, "blurred");
|
||
assert.equal(
|
||
window.getComputedStyle(root).getPropertyValue("--similar-card-thumb-url").trim(),
|
||
"",
|
||
);
|
||
|
||
img.src = remoteThumb;
|
||
img.dispatchEvent(new window.Event("load"));
|
||
|
||
assert.equal(cache.get("video-456"), remoteThumb);
|
||
assert.equal(
|
||
window.getComputedStyle(root).getPropertyValue("--similar-card-thumb-url").trim(),
|
||
"",
|
||
);
|
||
assert.equal(img.dataset.thumbnailState, "blurred");
|
||
});
|
||
|
||
test("primary clicks trigger onPlay while modifiers and right clicks do not", (t) => {
|
||
const { card, window } = renderCard(t, {
|
||
video: { id: "video-play" },
|
||
});
|
||
|
||
let callCount = 0;
|
||
card.onPlay = (payload) => {
|
||
callCount += 1;
|
||
assert.equal(payload.video.id, "video-play");
|
||
};
|
||
|
||
const mediaLink = card.getRoot().querySelector("a[data-primary-action=play]");
|
||
const titleLink = card.getRoot().querySelector(
|
||
".player-modal__similar-card-title",
|
||
);
|
||
assert(mediaLink);
|
||
assert(titleLink);
|
||
|
||
mediaLink.dispatchEvent(
|
||
new window.MouseEvent("click", { bubbles: true, button: 0 }),
|
||
);
|
||
assert.equal(callCount, 1);
|
||
|
||
mediaLink.dispatchEvent(
|
||
new window.MouseEvent("click", { bubbles: true, button: 0, metaKey: true }),
|
||
);
|
||
mediaLink.dispatchEvent(
|
||
new window.MouseEvent("click", { bubbles: true, button: 2 }),
|
||
);
|
||
assert.equal(callCount, 1);
|
||
|
||
titleLink.dispatchEvent(
|
||
new window.MouseEvent("click", { bubbles: true, button: 0 }),
|
||
);
|
||
assert.equal(callCount, 2);
|
||
});
|
||
|
||
test("author identity fields render supplied values and datasets", (t) => {
|
||
const identity = {
|
||
name: "Satoshi Nakamoto",
|
||
npub: "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
||
pubkey: "pubkey-satoshi",
|
||
picture: "https://cdn.example.com/avatar.png",
|
||
};
|
||
|
||
const { card } = renderCard(t, {
|
||
video: { id: "identity" },
|
||
identity,
|
||
});
|
||
|
||
const root = card.getRoot();
|
||
const nameEl = root.querySelector(".author-name");
|
||
// The npub element is intentionally detached from the DOM but retained in the instance
|
||
const npubEl = card.authorNpubEl;
|
||
const avatarEl = root.querySelector(
|
||
".player-modal__similar-card-avatar-img",
|
||
);
|
||
|
||
assert(nameEl);
|
||
assert(npubEl);
|
||
// Avatar is also detached/optional in the current component logic (see buildAuthorStack comments)
|
||
// assert(avatarEl);
|
||
|
||
assert.equal(nameEl.textContent, identity.name);
|
||
assert.equal(nameEl.dataset.pubkey, identity.pubkey);
|
||
|
||
const expectedShort = formatShortNpub(identity.npub);
|
||
assert.equal(npubEl.textContent, expectedShort);
|
||
assert.equal(npubEl.dataset.pubkey, identity.pubkey);
|
||
assert.equal(npubEl.dataset.npub, identity.npub);
|
||
assert.equal(npubEl.getAttribute("title"), identity.npub);
|
||
assert.equal(npubEl.hidden, false);
|
||
assert.equal(npubEl.getAttribute("aria-hidden"), "false");
|
||
|
||
if (avatarEl) {
|
||
assert.equal(avatarEl.dataset.pubkey, identity.pubkey);
|
||
assert.equal(avatarEl.src, identity.picture);
|
||
assert.match(avatarEl.alt, /Satoshi/);
|
||
}
|
||
});
|
||
|
||
test("view counter wiring exposes pointer datasets", (t) => {
|
||
const pointerInfo = {
|
||
key: "views:123",
|
||
pointer: ["a", "kind:views", "wss://relay.example"],
|
||
};
|
||
|
||
const { card } = renderCard(t, {
|
||
video: { id: "views" },
|
||
pointerInfo,
|
||
});
|
||
|
||
const viewEl = card.getViewCountElement();
|
||
assert(viewEl);
|
||
assert.equal(viewEl.dataset.viewPointer, pointerInfo.key);
|
||
assert.equal(viewEl.dataset.viewCount, "");
|
||
assert.equal(viewEl.textContent, "–");
|
||
|
||
const root = card.getRoot();
|
||
assert.equal(root.dataset.pointerKey, pointerInfo.key);
|
||
assert.equal(root.dataset.pointerType, pointerInfo.pointer[0]);
|
||
assert.equal(root.dataset.pointerValue, pointerInfo.pointer[1]);
|
||
assert.equal(root.dataset.pointerRelay, pointerInfo.pointer[2]);
|
||
});
|