Files
bitvid/tests/publish-outcome-message.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 3f4299a885 feat(publish): tell the user how many relays accepted their video
Publishing showed a flat "Video shared successfully!" even when most (or all)
relays rejected the event, so users couldn't tell a partial/failed publish from
a clean one. Now publishVideo captures the per-relay accept/reject summary and
stashes a compact {accepted,total} tally on the returned event via a
non-enumerable key (never serialized, never sent to relays). app.js reads it
back and reports the real outcome:
  - all relays accepted  -> "Video published to N relays!"
  - some rejected        -> warning "Video published to N of M relays …"
  - none accepted        -> error (defensive; this path normally throws first)
Falls back to the generic message when no tally is available.

Tests (describePublishOutcome + attach/readRelayPublishSummary): success/partial/
zero/singular/missing-tally messaging, and that the tally round-trips while
staying out of JSON serialization. Mutation-verified — flipping partial→success
and the summary key to enumerable both fail the suite.

Build + full lint green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 12:19:09 -04:00

73 lines
2.6 KiB
JavaScript

// After publishing a video bitvid must tell the user how many relays actually
// accepted it, instead of an opaque "shared successfully" that hides a partial
// or total relay failure. describePublishOutcome maps the relay tally to a
// message + tone; attach/readRelayPublishSummary carry the tally from the
// signed event to the UI without polluting the serialized event.
import assert from "node:assert/strict";
import test from "node:test";
import {
describePublishOutcome,
attachRelayPublishSummary,
readRelayPublishSummary,
RELAY_PUBLISH_SUMMARY_KEY,
} from "../js/nostrPublish.js";
test("full acceptance reports the relay count as success", () => {
const out = describePublishOutcome({ accepted: 4, total: 4 });
assert.equal(out.tone, "success");
assert.match(out.message, /4 relays/);
});
test("singular relay is not pluralized", () => {
const out = describePublishOutcome({ accepted: 1, total: 1 });
assert.equal(out.tone, "success");
assert.match(out.message, /1 relay\b/);
assert.doesNotMatch(out.message, /1 relays/);
});
test("partial acceptance is surfaced as a warning with both counts", () => {
const out = describePublishOutcome({ accepted: 2, total: 8 });
assert.equal(out.tone, "warning");
assert.match(out.message, /2 of 8/);
});
test("zero acceptance is an error telling the user nothing landed", () => {
const out = describePublishOutcome({ accepted: 0, total: 8 });
assert.equal(out.tone, "error");
assert.match(out.message, /any relay/i);
});
test("missing tally falls back to the generic success message", () => {
const out = describePublishOutcome({});
assert.equal(out.tone, "success");
// No fabricated relay count when we don't actually know one.
assert.doesNotMatch(out.message, /\d+ of \d+/);
});
test("attached tally round-trips and stays out of serialization", () => {
const event = { id: "abc", kind: 30078, content: "{}" };
attachRelayPublishSummary(event, {
accepted: [{ url: "wss://a" }, { url: "wss://b" }],
failed: [{ url: "wss://c" }],
});
const tally = readRelayPublishSummary(event);
assert.deepEqual(tally, { accepted: 2, total: 3 });
// The tally must never leak into the event the relays/JSON see.
assert.ok(
!Object.keys(event).includes(RELAY_PUBLISH_SUMMARY_KEY),
"summary key must be non-enumerable",
);
assert.ok(
!JSON.stringify(event).includes(RELAY_PUBLISH_SUMMARY_KEY),
"summary must not appear in the serialized event",
);
});
test("reading a summary off an event without one returns null", () => {
assert.equal(readRelayPublishSummary({ id: "x" }), null);
assert.equal(readRelayPublishSummary(null), null);
});