Files
bitvid/tests/watch-history-local-remove.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 2f95d4486e fix(watch-history): logged-out (local) delete now rewrites the local queue
The other half of the synced-vs-local design: for logged-out / session
(local-only) actors, watch history lives in a localStorage queue (loadLatest ->
collectQueueItems), but snapshot no-op'd for local-only actors — so a removal
never touched the queue and the item reappeared on reload (same symptom as the
logged-in bug, different layer).

Now a REPLACE snapshot for a local-only actor rewrites the local queue to exactly
the provided items (new replaceLocalQueue helper): removals prune it, an empty
replace clears it. Normal watch-tracking adds still flow through the queue via
recordVideoView, unchanged.

Tests + mutation-verified: local replace seeds then prunes the queue; empty
replace clears it. Reverting to the no-op fails both.

Build + full lint green (watchHistoryService under file-size budget).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 18:45:42 -04:00

73 lines
2.5 KiB
JavaScript

// Logged-out / local-only watch history lives in a localStorage queue, not on
// relays. A removal there is a REPLACE snapshot — it must rewrite the local queue
// so the deletion sticks. Previously snapshot no-op'd for local-only actors, so
// removing an item locally did nothing and it reappeared on reload.
import "./test-helpers/setup-localstorage.mjs";
import assert from "node:assert/strict";
import test from "node:test";
import { watchHistoryService } from "../js/watchHistoryService.js";
import { nostrClient } from "../js/nostrClientFacade.js";
// Make `actor` local-only by making it the session actor (feature disabled for
// the session actor → local queue path).
function makeLocalActor(actor) {
nostrClient.pubkey = "";
nostrClient.sessionActor = { pubkey: actor };
}
test("local-only replace snapshot seeds and then prunes the local queue", async () => {
const actor = "f".repeat(64);
makeLocalActor(actor);
// Seed three items via a replace snapshot (the local queue path).
const seed = await watchHistoryService.snapshot(
[
{ type: "e", value: "local-a" },
{ type: "e", value: "local-b" },
{ type: "e", value: "local-c" },
],
{ actor, replace: true },
);
assert.equal(seed.skipped, undefined, "local replace must NOT be skipped");
let items = await watchHistoryService.loadLatest(actor, { allowStale: false });
assert.deepEqual(
items.map((i) => i.value).sort(),
["local-a", "local-b", "local-c"],
"seeded local queue should have all three",
);
// Remove "local-b": replace with the remaining two.
await watchHistoryService.snapshot(
[
{ type: "e", value: "local-a" },
{ type: "e", value: "local-c" },
],
{ actor, replace: true },
);
items = await watchHistoryService.loadLatest(actor, { allowStale: false });
assert.deepEqual(
items.map((i) => i.value).sort(),
["local-a", "local-c"],
"removed item must be gone from the local queue (delete sticks)",
);
});
test("local-only clear (empty replace) empties the local queue", async () => {
const actor = "e".repeat(64);
makeLocalActor(actor);
await watchHistoryService.snapshot([{ type: "e", value: "only" }], {
actor,
replace: true,
});
let items = await watchHistoryService.loadLatest(actor, { allowStale: false });
assert.equal(items.length, 1);
await watchHistoryService.snapshot([], { actor, replace: true });
items = await watchHistoryService.loadLatest(actor, { allowStale: false });
assert.equal(items.length, 0, "clearing the last local item must empty the queue");
});