mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
183 lines
5.2 KiB
JavaScript
183 lines
5.2 KiB
JavaScript
import { describe, it, beforeEach } from "node:test";
|
|
import assert from "node:assert";
|
|
import ReactionController from "../../js/ui/reactionController.js";
|
|
|
|
describe("ReactionController", () => {
|
|
let controller;
|
|
let mockReactionCounter;
|
|
let mockVideoModal;
|
|
let mockState;
|
|
let mockCallbacks;
|
|
let mockUi;
|
|
|
|
beforeEach(() => {
|
|
mockReactionCounter = {
|
|
subscribe: (pointer, cb) => {
|
|
return () => {};
|
|
},
|
|
publish: async () => ({ ok: true })
|
|
};
|
|
|
|
mockVideoModal = {
|
|
updateReactionSummary: () => {},
|
|
setUserReaction: () => {}
|
|
};
|
|
|
|
mockUi = {
|
|
getVideoModal: () => mockVideoModal,
|
|
showError: () => {}
|
|
};
|
|
|
|
mockState = {
|
|
getCurrentVideo: () => ({ id: "v1", pubkey: "p1" }),
|
|
getCurrentVideoPointer: () => ["e", "v1"],
|
|
getCurrentVideoPointerKey: () => "e:v1"
|
|
};
|
|
|
|
mockCallbacks = {
|
|
isUserLoggedIn: () => true,
|
|
normalizeHexPubkey: (val) => val,
|
|
getPubkey: () => "user1"
|
|
};
|
|
|
|
controller = new ReactionController({
|
|
services: { reactionCounter: mockReactionCounter },
|
|
ui: mockUi,
|
|
state: mockState,
|
|
callbacks: mockCallbacks
|
|
});
|
|
});
|
|
|
|
it("should subscribe to reactions", (t) => {
|
|
const subscribeMock = t.mock.method(mockReactionCounter, "subscribe");
|
|
|
|
controller.subscribe(["e", "v1"], "e:v1");
|
|
|
|
assert.strictEqual(subscribeMock.mock.callCount(), 1);
|
|
assert.deepStrictEqual(subscribeMock.mock.calls[0].arguments[0], [
|
|
"e",
|
|
"v1"
|
|
]);
|
|
});
|
|
|
|
it("includes the immutable event ID when subscribing to addressable videos", (t) => {
|
|
const subscribeMock = t.mock.method(mockReactionCounter, "subscribe");
|
|
|
|
controller.subscribe(
|
|
["a", "34235:p1:episode-42", "wss://relay.example"],
|
|
"a:34235:p1:episode-42"
|
|
);
|
|
|
|
assert.deepStrictEqual(subscribeMock.mock.calls[0].arguments[0], {
|
|
type: "a",
|
|
value: "34235:p1:episode-42",
|
|
relay: "wss://relay.example",
|
|
eventId: "v1"
|
|
});
|
|
});
|
|
|
|
it("should handle reaction update from subscription", (t) => {
|
|
let subCallback;
|
|
mockReactionCounter.subscribe = (pointer, cb) => {
|
|
subCallback = cb;
|
|
return () => {};
|
|
};
|
|
const updateSummaryMock = t.mock.method(
|
|
mockVideoModal,
|
|
"updateReactionSummary"
|
|
);
|
|
|
|
controller.subscribe(["e", "v1"], "e:v1");
|
|
// subscribe calls unsubscribe which calls resetState which calls updateReactionSummary (1st call)
|
|
|
|
subCallback({
|
|
total: 10,
|
|
counts: { "+": 10 },
|
|
reactions: { user1: { content: "+" } }
|
|
});
|
|
// subCallback calls updateReactionSummary (2nd call)
|
|
|
|
assert.strictEqual(updateSummaryMock.mock.callCount(), 2);
|
|
assert.deepStrictEqual(updateSummaryMock.mock.calls[1].arguments[0], {
|
|
total: 10,
|
|
counts: { "+": 10, "-": 0 },
|
|
userReaction: "+"
|
|
});
|
|
});
|
|
|
|
it("should apply optimistic update on handleReaction", async (t) => {
|
|
const updateSummaryMock = t.mock.method(
|
|
mockVideoModal,
|
|
"updateReactionSummary"
|
|
);
|
|
const publishMock = t.mock.method(mockReactionCounter, "publish");
|
|
|
|
// Pre-seed state
|
|
controller.reactionState = {
|
|
total: 5,
|
|
counts: { "+": 5, "-": 0 },
|
|
userReaction: ""
|
|
};
|
|
|
|
await controller.handleReaction({ reaction: "+" });
|
|
|
|
// Verify optimistic update call
|
|
// Should increment + count and total
|
|
assert.strictEqual(updateSummaryMock.mock.callCount(), 1);
|
|
const updateArgs = updateSummaryMock.mock.calls[0].arguments[0];
|
|
assert.strictEqual(updateArgs.total, 6);
|
|
assert.strictEqual(updateArgs.counts["+"], 6);
|
|
assert.strictEqual(updateArgs.userReaction, "+");
|
|
|
|
// Verify publish call
|
|
assert.strictEqual(publishMock.mock.callCount(), 1);
|
|
});
|
|
|
|
it("should rollback optimistic update on publish failure", async (t) => {
|
|
mockReactionCounter.publish = async () => ({ ok: false });
|
|
const updateSummaryMock = t.mock.method(
|
|
mockVideoModal,
|
|
"updateReactionSummary"
|
|
);
|
|
|
|
// We need to update controller.ui.showError because constructor copied the reference
|
|
const showErrorMock = t.mock.method(mockUi, "showError");
|
|
controller.ui.showError = showErrorMock;
|
|
|
|
controller.reactionState = {
|
|
total: 5,
|
|
counts: { "+": 5, "-": 0 },
|
|
userReaction: ""
|
|
};
|
|
|
|
await controller.handleReaction({ reaction: "+" });
|
|
|
|
// 1st call: optimistic update
|
|
// 2nd call: restore snapshot
|
|
assert.strictEqual(updateSummaryMock.mock.callCount(), 2);
|
|
|
|
// Restore check
|
|
const restoreArgs = updateSummaryMock.mock.calls[1].arguments[0];
|
|
assert.strictEqual(restoreArgs.total, 5);
|
|
assert.strictEqual(restoreArgs.counts["+"], 5);
|
|
assert.strictEqual(restoreArgs.userReaction, "");
|
|
|
|
assert.strictEqual(showErrorMock.mock.callCount(), 1);
|
|
});
|
|
|
|
it("should not react if user not logged in", async (t) => {
|
|
// Update callback on controller instance
|
|
controller.callbacks.isUserLoggedIn = () => false;
|
|
|
|
const showErrorMock = t.mock.method(mockUi, "showError");
|
|
controller.ui.showError = showErrorMock;
|
|
|
|
const publishMock = t.mock.method(mockReactionCounter, "publish");
|
|
|
|
await controller.handleReaction({ reaction: "+" });
|
|
|
|
assert.strictEqual(showErrorMock.mock.callCount(), 1);
|
|
assert.strictEqual(publishMock.mock.callCount(), 0);
|
|
});
|
|
});
|