Files
bitvid/tests/services/discussionCountService.test.mjs
T
google-labs-jules[bot]andPR0M3TH3AN 1ac9fee2ed feat(test): Add DiscussionCountService error handling tests
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]>
2026-02-08 00:24:54 +00:00

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, "—");
});
});