mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Watch-history cards rendered "Untitled video" / "Unknown" for newly watched videos. nostrClient.allEvents stores CONVERTED video objects (convertEventToVideo), which carry tags/pubkey but no `kind` field — only raw relay events do. resolveEventAddress() did Number(event.kind) → NaN and bailed with "", so the address (cache-by-"a"-pointer) scan matched nothing. Every video pointer (all are "a" addresses) was forced to a relay round-trip, and when that returned nothing the card showed Unknown — even though the just-watched video was sitting right there in allEvents. Fix: default to the video kind (30078) when a cached object has no `kind`, so the local cache scan resolves freshly-watched "a" pointers without a relay round-trip. Adds tests/watch-history-hydration-cache.test.mjs: relays return nothing, so a pass can only come from the cache scan (mutation-verified against the NaN-reject regression). Exports createWatchHistoryHydrationStage for boundary testing. Co-Authored-By: Claude Opus 4.8 <[email protected]>
91 lines
2.9 KiB
JavaScript
91 lines
2.9 KiB
JavaScript
// A freshly-watched video is already in nostrClient.allEvents (you just played
|
|
// it). The watch-history feed stores an "a" (address) pointer for it and must
|
|
// resolve the title/thumbnail from that local cache WITHOUT a relay round-trip.
|
|
//
|
|
// Bug: allEvents holds *converted* video objects (convertEventToVideo), which
|
|
// carry tags/pubkey but NO `kind` field. resolveEventAddress did Number(kind) →
|
|
// NaN → returned "" for every cached video, so the address cache-scan never
|
|
// matched. Every video pointer fell through to a relay fetch, and when that came
|
|
// up empty the card rendered "Untitled video" / "Unknown".
|
|
//
|
|
// Correct behavior: the cache-scan resolves an "a" pointer to the cached video
|
|
// using the video kind by default, so freshly-watched videos show their real
|
|
// metadata immediately — even when relays return nothing.
|
|
|
|
import "./test-helpers/setup-localstorage.mjs";
|
|
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { createWatchHistoryHydrationStage } from "../js/feedEngine/watchHistoryFeed.js";
|
|
import { nostrClient } from "../js/nostrClientFacade.js";
|
|
|
|
const PUBKEY = "c".repeat(64);
|
|
const DTAG = "my-video-root-id";
|
|
const ADDRESS = `30078:${PUBKEY}:${DTAG}`;
|
|
|
|
// Mirrors convertEventToVideo output: tags + pubkey, but NO `kind`.
|
|
function cachedVideoWithoutKind() {
|
|
return {
|
|
id: "e".repeat(64),
|
|
pubkey: PUBKEY,
|
|
title: "My Real Video Title",
|
|
thumbnail: "https://example.com/thumb.jpg",
|
|
tags: [["d", DTAG]],
|
|
created_at: 1750000000,
|
|
invalid: false,
|
|
};
|
|
}
|
|
|
|
test("an 'a'-pointer resolves from the local cache (no relay needed) for a converted video lacking `kind`", async () => {
|
|
const prevAllEvents = nostrClient.allEvents;
|
|
const prevPool = nostrClient.pool;
|
|
const prevRead = nostrClient.readRelays;
|
|
|
|
nostrClient.allEvents = new Map([
|
|
[cachedVideoWithoutKind().id, cachedVideoWithoutKind()],
|
|
]);
|
|
// Relays return nothing — so a pass can ONLY come from the cache scan.
|
|
let relayHits = 0;
|
|
nostrClient.pool = {
|
|
list: async () => {
|
|
relayHits += 1;
|
|
return [];
|
|
},
|
|
};
|
|
nostrClient.readRelays = ["wss://relay.example"];
|
|
|
|
try {
|
|
const stage = createWatchHistoryHydrationStage();
|
|
const items = [
|
|
{
|
|
pointer: { type: "a", value: ADDRESS },
|
|
pointerKey: `a:${ADDRESS}`,
|
|
watchedAt: 1750000001,
|
|
video: null,
|
|
metadata: {},
|
|
},
|
|
];
|
|
|
|
const result = await stage(items, {});
|
|
|
|
assert.equal(result.length, 1);
|
|
assert.ok(
|
|
result[0].video,
|
|
"freshly-watched video must resolve from the local cache",
|
|
);
|
|
assert.equal(
|
|
result[0].video.title,
|
|
"My Real Video Title",
|
|
"resolved video must carry the real title (not Unknown)",
|
|
);
|
|
assert.equal(
|
|
relayHits,
|
|
0,
|
|
"cache hit must avoid a relay round-trip entirely",
|
|
);
|
|
} finally {
|
|
nostrClient.allEvents = prevAllEvents;
|
|
nostrClient.pool = prevPool;
|
|
nostrClient.readRelays = prevRead;
|
|
}
|
|
});
|