mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Add comprehensive tests for `DiscussionCountService` focusing on error handling, input validation, and caching. - Covers happy path, partial results, and unsupported relays. - Validates graceful degradation when `nostrClient` calls fail. - Ensures no regression in service functionality. Co-authored-by: PR0M3TH3AN <[email protected]>
193 lines
6.7 KiB
JavaScript
193 lines
6.7 KiB
JavaScript
import { describe, test, mock, before, after, beforeEach } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createUiDom } from "../ui/helpers/jsdom-test-helpers.mjs";
|
|
import DiscussionCountService from "../../js/services/discussionCountService.js";
|
|
|
|
describe("DiscussionCountService", () => {
|
|
let dom;
|
|
let document;
|
|
let window;
|
|
|
|
before(() => {
|
|
dom = createUiDom();
|
|
window = dom.window;
|
|
document = dom.document;
|
|
});
|
|
|
|
after(() => {
|
|
dom.cleanup();
|
|
});
|
|
|
|
test("initialization sets defaults", () => {
|
|
const service = new DiscussionCountService();
|
|
assert.equal(service.maxVideos, 24);
|
|
assert.ok(service.logger);
|
|
assert.equal(service.videoDiscussionCountCache.size, 0);
|
|
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
|
});
|
|
|
|
test("refreshCounts returns early if videos array is empty or invalid", () => {
|
|
const service = new DiscussionCountService();
|
|
// mock logger to ensure no warnings
|
|
const logger = { warn: mock.fn() };
|
|
service.logger = logger;
|
|
|
|
service.refreshCounts([], {});
|
|
service.refreshCounts(null, {});
|
|
|
|
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
|
assert.equal(logger.warn.mock.callCount(), 0);
|
|
});
|
|
|
|
test("refreshCounts returns early if dependencies are missing", () => {
|
|
const service = new DiscussionCountService();
|
|
const video = { id: "video1" };
|
|
|
|
// Missing nostrClient
|
|
service.refreshCounts([video], { videoListRoot: document.createElement("div") });
|
|
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
|
|
|
// Missing videoListRoot
|
|
service.refreshCounts([video], { nostrClient: {} });
|
|
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
|
});
|
|
|
|
test("refreshCounts handles happy path (fetches and updates DOM)", async () => {
|
|
const service = new DiscussionCountService();
|
|
const video = { id: "video1", kind: 30078, pubkey: "pubkey1" };
|
|
|
|
const root = document.createElement("div");
|
|
const container = document.createElement("div");
|
|
container.dataset.discussionCount = "video1";
|
|
const valueEl = document.createElement("span");
|
|
valueEl.dataset.discussionCountValue = "true";
|
|
container.appendChild(valueEl);
|
|
root.appendChild(container);
|
|
|
|
const nostrClient = {
|
|
pool: {},
|
|
countEventsAcrossRelays: mock.fn(async () => ({
|
|
total: 42,
|
|
ok: true,
|
|
perRelay: [{ ok: true }]
|
|
}))
|
|
};
|
|
|
|
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
|
|
|
// Wait for promise resolution (microtasks)
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
|
|
assert.equal(nostrClient.countEventsAcrossRelays.mock.callCount(), 1);
|
|
assert.equal(valueEl.textContent, "42");
|
|
assert.equal(container.dataset.countState, "ready");
|
|
assert.equal(service.videoDiscussionCountCache.get("video1"), 42);
|
|
});
|
|
|
|
test("refreshCounts uses cached count if available", async () => {
|
|
const service = new DiscussionCountService();
|
|
const video = { id: "video1" };
|
|
service.videoDiscussionCountCache.set("video1", 100);
|
|
|
|
const root = document.createElement("div");
|
|
const container = document.createElement("div");
|
|
container.dataset.discussionCount = "video1";
|
|
const valueEl = document.createElement("span");
|
|
valueEl.dataset.discussionCountValue = "true";
|
|
container.appendChild(valueEl);
|
|
root.appendChild(container);
|
|
|
|
const nostrClient = {
|
|
pool: {},
|
|
countEventsAcrossRelays: mock.fn()
|
|
};
|
|
|
|
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
|
|
|
assert.equal(nostrClient.countEventsAcrossRelays.mock.callCount(), 0);
|
|
assert.equal(valueEl.textContent, "100");
|
|
});
|
|
|
|
test("refreshCounts handles API errors gracefully", async () => {
|
|
const logger = { warn: mock.fn() };
|
|
const service = new DiscussionCountService({ logger });
|
|
const video = { id: "video1" };
|
|
|
|
const root = document.createElement("div");
|
|
const container = document.createElement("div");
|
|
container.dataset.discussionCount = "video1";
|
|
const valueEl = document.createElement("span");
|
|
valueEl.dataset.discussionCountValue = "true";
|
|
container.appendChild(valueEl);
|
|
root.appendChild(container);
|
|
|
|
const error = new Error("Network error");
|
|
const nostrClient = {
|
|
pool: {},
|
|
countEventsAcrossRelays: mock.fn(async () => { throw error; })
|
|
};
|
|
|
|
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
|
|
|
const requestPromise = service.inFlightDiscussionCounts.get("video1");
|
|
await assert.rejects(requestPromise, error);
|
|
|
|
assert.equal(logger.warn.mock.callCount(), 1);
|
|
assert.equal(container.dataset.countState, "error");
|
|
assert.equal(valueEl.textContent, "—");
|
|
});
|
|
|
|
test("refreshCounts handles partial results", async () => {
|
|
const service = new DiscussionCountService();
|
|
const video = { id: "video1" };
|
|
|
|
const root = document.createElement("div");
|
|
const container = document.createElement("div");
|
|
container.dataset.discussionCount = "video1";
|
|
const valueEl = document.createElement("span");
|
|
valueEl.dataset.discussionCountValue = "true";
|
|
container.appendChild(valueEl);
|
|
root.appendChild(container);
|
|
|
|
const nostrClient = {
|
|
pool: {},
|
|
countEventsAcrossRelays: mock.fn(async () => ({ total: 5, partial: true, ok: true, perRelay: [{ ok: true }] }))
|
|
};
|
|
|
|
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
|
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
|
|
assert.equal(container.dataset.countState, "partial");
|
|
assert.equal(valueEl.textContent, "5");
|
|
});
|
|
|
|
test("refreshCounts handles unsupported relays (empty perRelay)", async () => {
|
|
const service = new DiscussionCountService();
|
|
const video = { id: "video1" };
|
|
|
|
const root = document.createElement("div");
|
|
const container = document.createElement("div");
|
|
container.dataset.discussionCount = "video1";
|
|
const valueEl = document.createElement("span");
|
|
valueEl.dataset.discussionCountValue = "true";
|
|
container.appendChild(valueEl);
|
|
root.appendChild(container);
|
|
|
|
const nostrClient = {
|
|
pool: {},
|
|
countEventsAcrossRelays: mock.fn(async () => ({ total: 0, perRelay: [] }))
|
|
};
|
|
|
|
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
|
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
await new Promise(resolve => process.nextTick(resolve));
|
|
|
|
assert.equal(container.dataset.countState, "unsupported");
|
|
assert.equal(valueEl.textContent, "—");
|
|
});
|
|
});
|