mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Allow comments without definition address
This commit is contained in:
1 parent
38d706737f
commit
a4e6db7151
4 files changed
+180
-10
No files matched your search
@@ -141,7 +141,7 @@ export default class CommentThreadService {
|
||||
this.parentCommentId = normalizeString(parentCommentId);
|
||||
this.activeRelays = Array.isArray(relays) ? [...relays] : null;
|
||||
|
||||
if (!this.videoEventId || !this.videoAddressPointer) {
|
||||
if (!this.videoEventId) {
|
||||
this.emitError(
|
||||
new Error("Unable to resolve video pointer for comment thread."),
|
||||
);
|
||||
@@ -158,10 +158,13 @@ export default class CommentThreadService {
|
||||
const fetchLimit = toPositiveInteger(limit, this.defaultLimit);
|
||||
const target = {
|
||||
videoEventId: this.videoEventId,
|
||||
videoDefinitionAddress: this.videoAddressPointer,
|
||||
parentCommentId: this.parentCommentId,
|
||||
};
|
||||
|
||||
if (this.videoAddressPointer) {
|
||||
target.videoDefinitionAddress = this.videoAddressPointer;
|
||||
}
|
||||
|
||||
let events = [];
|
||||
try {
|
||||
events = await this.fetchVideoComments(target, {
|
||||
|
||||
@@ -132,7 +132,7 @@ export default class VideoModalCommentController {
|
||||
const videoEventId = normalizeString(video.id);
|
||||
const videoDefinitionAddress = buildVideoAddressPointer(video);
|
||||
|
||||
if (!videoEventId || !videoDefinitionAddress) {
|
||||
if (!videoEventId) {
|
||||
this.resetModalCommentState({ hide: false });
|
||||
this.videoModal.setCommentStatus?.(
|
||||
"Comments are unavailable for this video.",
|
||||
@@ -142,7 +142,7 @@ export default class VideoModalCommentController {
|
||||
|
||||
this.modalCommentState = {
|
||||
videoEventId,
|
||||
videoDefinitionAddress,
|
||||
videoDefinitionAddress: videoDefinitionAddress || null,
|
||||
parentCommentId: null,
|
||||
};
|
||||
this.modalCommentProfiles = new Map();
|
||||
@@ -487,7 +487,7 @@ export default class VideoModalCommentController {
|
||||
const videoEventId = normalizeString(video.id);
|
||||
const videoDefinitionAddress = buildVideoAddressPointer(video);
|
||||
|
||||
if (!videoEventId || !videoDefinitionAddress) {
|
||||
if (!videoEventId) {
|
||||
this.callbacks.showError("Comments are unavailable for this video.");
|
||||
return;
|
||||
}
|
||||
@@ -499,13 +499,18 @@ export default class VideoModalCommentController {
|
||||
reason: "submitting",
|
||||
});
|
||||
|
||||
const publishPayload = {
|
||||
videoEventId,
|
||||
parentCommentId,
|
||||
};
|
||||
|
||||
if (videoDefinitionAddress) {
|
||||
publishPayload.videoDefinitionAddress = videoDefinitionAddress;
|
||||
}
|
||||
|
||||
const publishPromise = Promise.resolve(
|
||||
this.services.publishComment(
|
||||
{
|
||||
videoEventId,
|
||||
videoDefinitionAddress,
|
||||
parentCommentId,
|
||||
},
|
||||
publishPayload,
|
||||
{
|
||||
content: text,
|
||||
},
|
||||
|
||||
@@ -205,6 +205,72 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"CommentThreadService loadThread falls back to event id when address is missing",
|
||||
async () => {
|
||||
const video = {
|
||||
...createBaseVideo(),
|
||||
tags: [["alt", "ignored"]],
|
||||
};
|
||||
|
||||
const fetchTargets = [];
|
||||
const fetchOptions = [];
|
||||
const subscribeTargets = [];
|
||||
const subscribeOptions = [];
|
||||
const errors = [];
|
||||
|
||||
const service = new CommentThreadService({
|
||||
fetchVideoComments: async (target, options) => {
|
||||
fetchTargets.push(target);
|
||||
fetchOptions.push(options);
|
||||
return [];
|
||||
},
|
||||
subscribeVideoComments: (target, options = {}) => {
|
||||
subscribeTargets.push(target);
|
||||
subscribeOptions.push(options);
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
service.setCallbacks({
|
||||
onError: (error) => errors.push(error),
|
||||
});
|
||||
|
||||
const snapshot = await service.loadThread({ video });
|
||||
|
||||
assert.equal(fetchTargets.length, 1, "fetch should run even without an address");
|
||||
assert.deepStrictEqual(fetchTargets[0], {
|
||||
videoEventId: "video123",
|
||||
parentCommentId: "",
|
||||
});
|
||||
assert.equal(
|
||||
"videoDefinitionAddress" in fetchTargets[0],
|
||||
false,
|
||||
"videoDefinitionAddress should be omitted when unavailable",
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(fetchOptions[0], {
|
||||
limit: service.defaultLimit,
|
||||
relays: null,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
subscribeTargets.length,
|
||||
1,
|
||||
"subscribe should run even without an address",
|
||||
);
|
||||
assert.deepStrictEqual(subscribeTargets[0], fetchTargets[0]);
|
||||
assert.equal(
|
||||
typeof subscribeOptions[0]?.onEvent,
|
||||
"function",
|
||||
"subscription handler should be provided",
|
||||
);
|
||||
|
||||
assert.equal(errors.length, 0, "missing address should not emit errors");
|
||||
assert.equal(snapshot.videoEventId, "video123");
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"CommentThreadService teardown cancels hydration timers",
|
||||
async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { setupModal } from "./video-modal-accessibility.test.mjs";
|
||||
import VideoModalCommentController from "../js/ui/videoModalCommentController.js";
|
||||
|
||||
const FOCUSABLE_SELECTOR = [
|
||||
"a[href]",
|
||||
@@ -174,6 +175,101 @@ test("VideoModal comment composer updates messaging and dispatches events", asyn
|
||||
assert.strictEqual(submitDetail.triggerElement, modal.commentsSubmitButton);
|
||||
});
|
||||
|
||||
test(
|
||||
"VideoModalCommentController publishes comment using event id fallback",
|
||||
async () => {
|
||||
const publishCalls = [];
|
||||
const statusMessages = [];
|
||||
let resetCalled = false;
|
||||
const composerStates = [];
|
||||
const appendedEvents = [];
|
||||
|
||||
const videoModal = {
|
||||
setCommentComposerState: (state) => {
|
||||
composerStates.push({ ...state });
|
||||
},
|
||||
resetCommentComposer: () => {
|
||||
resetCalled = true;
|
||||
},
|
||||
setCommentStatus: (message) => {
|
||||
statusMessages.push(message);
|
||||
},
|
||||
appendComment: (event) => {
|
||||
appendedEvents.push(event);
|
||||
},
|
||||
};
|
||||
|
||||
const processedEvents = [];
|
||||
|
||||
const controller = new VideoModalCommentController({
|
||||
commentThreadService: {
|
||||
setCallbacks: () => {},
|
||||
processIncomingEvent: (event) => {
|
||||
processedEvents.push(event);
|
||||
},
|
||||
},
|
||||
videoModal,
|
||||
auth: {
|
||||
isLoggedIn: () => true,
|
||||
},
|
||||
services: {
|
||||
publishComment: async (payload, eventData) => {
|
||||
publishCalls.push([payload, eventData]);
|
||||
return {
|
||||
ok: true,
|
||||
event: {
|
||||
id: "legacy-comment",
|
||||
pubkey: "pk1",
|
||||
content: eventData.content,
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
utils: {
|
||||
normalizeHexPubkey: (value) => value?.toLowerCase?.() || value,
|
||||
},
|
||||
});
|
||||
|
||||
controller.currentVideo = {
|
||||
id: "legacy-video",
|
||||
pubkey: "AUTHORPK",
|
||||
enableComments: true,
|
||||
kind: 30078,
|
||||
tags: [],
|
||||
};
|
||||
controller.modalCommentState.videoEventId = "legacy-video";
|
||||
|
||||
await controller.handleVideoModalCommentSubmit({ text: "Legacy support" });
|
||||
|
||||
assert.equal(publishCalls.length, 1, "publishComment should be invoked");
|
||||
const [payload, eventData] = publishCalls[0];
|
||||
assert.deepStrictEqual(payload, {
|
||||
videoEventId: "legacy-video",
|
||||
parentCommentId: null,
|
||||
});
|
||||
assert.equal(
|
||||
"videoDefinitionAddress" in payload,
|
||||
false,
|
||||
"payload should omit videoDefinitionAddress when unavailable",
|
||||
);
|
||||
assert.equal(eventData.content, "Legacy support");
|
||||
|
||||
assert.equal(processedEvents.length, 1, "event should be processed optimistically");
|
||||
assert.equal(resetCalled, true, "composer should reset after publishing");
|
||||
assert.ok(
|
||||
statusMessages.includes("Comment posted."),
|
||||
"success message should be surfaced",
|
||||
);
|
||||
assert.equal(
|
||||
composerStates[composerStates.length - 1]?.disabled,
|
||||
false,
|
||||
"composer should be re-enabled after publishing",
|
||||
);
|
||||
assert.equal(appendedEvents.length, 0, "optimistic insert should avoid duplicate append");
|
||||
},
|
||||
);
|
||||
|
||||
test("VideoModal comment section exposes aria landmarks and participates in focus trap", async (t) => {
|
||||
const { window, document, modal, playerModal, trigger, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
|
||||
Reference in new issue
Block a user