mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Merge pull request #1054 from PR0M3TH3AN/codex/add-tests-for-mixed-case-ids-and-exceptions
Add comment thread and modal regression tests
This commit is contained in:
2 files changed
+185
No files matched your search
@@ -187,6 +187,39 @@ test("CommentThreadService surfaces cache write failures", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("CommentThreadService persists caches safely during teardown failures", async () => {
|
||||
const warnings = [];
|
||||
const service = new CommentThreadService({
|
||||
fetchVideoComments: async () => [
|
||||
createComment({ id: "persist-me", pubkey: "pk-cache", createdAt: 1 }),
|
||||
],
|
||||
subscribeVideoComments: () => () => {},
|
||||
logger: {
|
||||
warn: (...args) => warnings.push(args),
|
||||
dev: { warn: (...args) => warnings.push(args) },
|
||||
user: { warn: (...args) => warnings.push(args) },
|
||||
},
|
||||
});
|
||||
|
||||
await service.loadThread({ video: createBaseVideo() });
|
||||
|
||||
globalThis.localStorage.setItem = () => {
|
||||
throw new Error("storage blocked");
|
||||
};
|
||||
|
||||
assert.doesNotThrow(() => service.teardown());
|
||||
assert.equal(
|
||||
warnings.some((args) =>
|
||||
args.some?.((value) =>
|
||||
typeof value === "string" &&
|
||||
value.includes("Failed to write comment cache for video123"),
|
||||
),
|
||||
),
|
||||
true,
|
||||
"teardown should warn when persistence fails but still reset state",
|
||||
);
|
||||
});
|
||||
|
||||
test("CommentThreadService logs cache usage and fallback decisions", async () => {
|
||||
const initialFlag = FEATURE_IMPROVED_COMMENT_FETCHING;
|
||||
const devLogs = [];
|
||||
@@ -416,6 +449,51 @@ test("CommentThreadService normalizes mixed-case pubkeys during hydration", asyn
|
||||
);
|
||||
});
|
||||
|
||||
test("CommentThreadService deduplicates mixed-case event ids and pubkeys", async () => {
|
||||
const hydrationRequests = [];
|
||||
const service = new CommentThreadService({
|
||||
fetchVideoComments: async () => [],
|
||||
subscribeVideoComments: () => () => {},
|
||||
batchFetchProfiles: async (pubkeys) => {
|
||||
hydrationRequests.push([...pubkeys]);
|
||||
},
|
||||
hydrationDebounceMs: 0,
|
||||
});
|
||||
|
||||
await service.loadThread({ video: createBaseVideo() });
|
||||
|
||||
const mixedCaseEvent = createComment({
|
||||
id: "CoMmEnT-123", // intentionally mixed case
|
||||
pubkey: "PuBkEy-123",
|
||||
createdAt: 10,
|
||||
});
|
||||
const lowerCaseEvent = createComment({
|
||||
id: "comment-123",
|
||||
pubkey: "pubkey-123",
|
||||
createdAt: 20,
|
||||
});
|
||||
|
||||
service.processIncomingEvent(mixedCaseEvent);
|
||||
service.processIncomingEvent(lowerCaseEvent);
|
||||
await service.flushProfileQueue();
|
||||
|
||||
assert.deepEqual(
|
||||
service.getCommentIdsForParent(null),
|
||||
["comment-123"],
|
||||
"mixed-case duplicates should collapse to a single normalized id",
|
||||
);
|
||||
assert.equal(
|
||||
service.getCommentEvent("COMMENT-123")?.created_at,
|
||||
20,
|
||||
"newer updates should replace existing mixed-case entries",
|
||||
);
|
||||
assert.deepEqual(
|
||||
hydrationRequests,
|
||||
[["pubkey-123"]],
|
||||
"hydration should only request the normalized pubkey once",
|
||||
);
|
||||
});
|
||||
|
||||
test("CommentThreadService retries profile hydration before succeeding", async () => {
|
||||
const profiles = new Map();
|
||||
const hydrationCalls = [];
|
||||
|
||||
@@ -699,6 +699,113 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"VideoModalCommentController disposes safely during in-flight comment loads",
|
||||
async () => {
|
||||
const renderCalls = [];
|
||||
const appendCalls = [];
|
||||
const teardownCalls = [];
|
||||
const setCallbacksCalls = [];
|
||||
let onThreadReady = null;
|
||||
let onCommentsAppended = null;
|
||||
let resolveLoadThread;
|
||||
|
||||
const commentThreadService = {
|
||||
defaultLimit: 5,
|
||||
setCallbacks: ({ onThreadReady: ready, onCommentsAppended: appended }) => {
|
||||
onThreadReady = ready;
|
||||
onCommentsAppended = appended;
|
||||
},
|
||||
loadThread: () =>
|
||||
new Promise((resolve) => {
|
||||
resolveLoadThread = resolve;
|
||||
}),
|
||||
teardown: () => {
|
||||
teardownCalls.push("teardown");
|
||||
},
|
||||
processIncomingEvent: () => {},
|
||||
};
|
||||
|
||||
const videoModal = {
|
||||
setCommentSectionCallbacks: (callbacks) => {
|
||||
setCallbacksCalls.push(callbacks);
|
||||
},
|
||||
setCommentsVisibility: () => {},
|
||||
hideCommentsDisabledMessage: () => {},
|
||||
showCommentsDisabledMessage: () => {},
|
||||
clearComments: () => {},
|
||||
resetCommentComposer: () => {},
|
||||
setCommentStatus: () => {},
|
||||
setCommentComposerState: () => {},
|
||||
renderComments: (payload) => renderCalls.push(payload),
|
||||
appendComment: (payload) => appendCalls.push(payload),
|
||||
};
|
||||
|
||||
const controller = new VideoModalCommentController({
|
||||
commentThreadService,
|
||||
videoModal,
|
||||
auth: { isLoggedIn: () => true },
|
||||
callbacks: {
|
||||
showError: () => {},
|
||||
showStatus: () => {},
|
||||
muteAuthor: () => Promise.resolve(),
|
||||
shouldHideAuthor: () => false,
|
||||
},
|
||||
services: {
|
||||
publishComment: async () => ({ ok: true, event: { id: "event", tags: [] } }),
|
||||
},
|
||||
});
|
||||
|
||||
controller.load({
|
||||
id: "video-inflight",
|
||||
pubkey: "AUTHORPK",
|
||||
enableComments: true,
|
||||
kind: 30078,
|
||||
tags: [],
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
teardownCalls.length = 0; // ignore the initial dispose inside load
|
||||
|
||||
controller.dispose();
|
||||
|
||||
assert.equal(teardownCalls.length, 1, "dispose should teardown the active thread");
|
||||
assert.equal(
|
||||
setCallbacksCalls.length > 0,
|
||||
true,
|
||||
"comment callbacks should be registered even before load resolves",
|
||||
);
|
||||
|
||||
resolveLoadThread?.();
|
||||
await Promise.resolve();
|
||||
|
||||
onThreadReady?.({
|
||||
videoEventId: "video-inflight",
|
||||
parentCommentId: null,
|
||||
commentsById: new Map([["event-1", { id: "event-1", pubkey: "pk1" }]]),
|
||||
childrenByParent: new Map([[null, ["event-1"]]]),
|
||||
profiles: new Map(),
|
||||
});
|
||||
|
||||
assert.equal(renderCalls.length, 0, "stale thread payloads should be ignored after dispose");
|
||||
|
||||
onCommentsAppended?.({
|
||||
videoEventId: "video-inflight",
|
||||
parentCommentId: null,
|
||||
commentIds: ["event-1"],
|
||||
commentsById: new Map([["event-1", { id: "event-1", pubkey: "pk1" }]]),
|
||||
childrenByParent: new Map([[null, ["event-1"]]]),
|
||||
profiles: new Map(),
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
appendCalls.length,
|
||||
0,
|
||||
"stale appends should not render duplicate comments after dispose",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"VideoModalCommentController prompts login when publish requires authentication",
|
||||
async () => {
|
||||
|
||||
Reference in new issue
Block a user