test(sidebar): make the two accepted-residual tests faithful to their contract

The hard-eviction-branch test asserted only the rev tuple outcome on two
one-entry stores while its comment claimed the >500-entry eviction/remount
setup. Build the real fixture: >500 equal-updatedAt entries so the bound
evicts the target by the id tiebreak, then a remounted rev-1 click merged
against the retained rev-100 remote, asserting the deterministic rev-100
outcome in both merge orders. Stars and mutes.

The unobserved-future mixed-fleet residual was stated but never exercised
directly: the fast-clock suites cover only the observed-future fix. Add a
click with an empty high-water at t, then a genuinely unobserved
opposite-value head at t+300 that wins on the primary updatedAt key. Both
hook suites.

Test-only; no production source changes.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
Duncan
2026-08-25 12:56:50 -04:00
committed by Will Pfleger
parent 439df98240
commit 16aeab68b0
4 changed files with 199 additions and 18 deletions

View File

@@ -378,19 +378,47 @@ test("finding 3 easy branch: a fresh click beats an evicted high-rev entry at an
// here as the hard branch (not disguised as safety).
test("finding 3 hard branch: equal-second evicted click (rev 1) loses to observed remote (rev 100)", () => {
const NOW = 777;
// Remount click on the evicted channel: empty high-water → rev 1, updatedAt=NOW.
const click = S(E(true, NOW, 1));
// The previously observed remote for the same channel at the same second,
// rev 100 (it may precede the remount — not genuinely concurrent).
const remote = S(E(false, NOW, 100));
const merged = mergeStores(click, remote);
const TARGET = "aaa-target"; // lexicographically small → evicted by the id tiebreak
// >500 entries all at the CURRENT second (NOW). With MAX+1 equal-updatedAt
// entries, boundMuteStore sorts ascending by (updatedAt, id) and keeps the
// highest MAX, so the lowest id is evicted — TARGET, NOT because it is old.
const channels = { [TARGET]: E(true, NOW, 7) };
for (let i = 0; i < MAX_CHANNEL_MUTE_ENTRIES; i++) {
channels[`z-${String(i).padStart(3, "0")}`] = E(true, NOW, 0);
}
const bounded = boundMuteStore({ version: 1, channels });
assert.equal(
Object.keys(bounded.channels).length,
MAX_CHANNEL_MUTE_ENTRIES,
"bound trims to the cap",
);
assert.equal(
bounded.channels[TARGET],
undefined,
"TARGET evicted by the id tiebreak at equal updatedAt",
);
// Remount in the same second: TARGET's rev high-water is gone with the entry,
// so a fresh click mints rev 1 at updatedAt=NOW.
const click = { version: 1, channels: { [TARGET]: E(true, NOW, 1) } };
// The previously observed remote for TARGET at the same second, rev 100 (it
// may precede the remount — not genuinely concurrent).
const remote = { version: 1, channels: { [TARGET]: E(false, NOW, 100) } };
// equal updatedAt → rev decides → 100 > 1: the click LOSES. Documented
// deterministic residual, proven here through real eviction+remount, not a
// pre-shrunk tuple. Deterministic in both merge orders — a lost click, never
// a divergence.
assert.deepEqual(
merged.channels.c,
mergeStores(click, remote).channels[TARGET],
E(false, NOW, 100),
"equal updatedAt → higher rev wins deterministically (documented residual)",
);
// Deterministic either merge order — a lost click, never a divergence.
assert.deepEqual(mergeStores(remote, click).channels.c, E(false, NOW, 100));
assert.deepEqual(
mergeStores(remote, click).channels[TARGET],
E(false, NOW, 100),
);
});
// ── mutedChannelIdsFromStore ────────────────────────────────────────────────

View File

@@ -378,19 +378,47 @@ test("finding 3 easy branch: a fresh click beats an evicted high-rev entry at an
// here as the hard branch (not disguised as safety).
test("finding 3 hard branch: equal-second evicted click (rev 1) loses to observed remote (rev 100)", () => {
const NOW = 777;
// Remount click on the evicted channel: empty high-water → rev 1, updatedAt=NOW.
const click = S(E(true, NOW, 1));
// The previously observed remote for the same channel at the same second,
// rev 100 (it may precede the remount — not genuinely concurrent).
const remote = S(E(false, NOW, 100));
const merged = mergeStores(click, remote);
const TARGET = "aaa-target"; // lexicographically small → evicted by the id tiebreak
// >500 entries all at the CURRENT second (NOW). With MAX+1 equal-updatedAt
// entries, boundStarStore sorts ascending by (updatedAt, id) and keeps the
// highest MAX, so the lowest id is evicted — TARGET, NOT because it is old.
const channels = { [TARGET]: E(true, NOW, 7) };
for (let i = 0; i < MAX_CHANNEL_STAR_ENTRIES; i++) {
channels[`z-${String(i).padStart(3, "0")}`] = E(true, NOW, 0);
}
const bounded = boundStarStore({ version: 1, channels });
assert.equal(
Object.keys(bounded.channels).length,
MAX_CHANNEL_STAR_ENTRIES,
"bound trims to the cap",
);
assert.equal(
bounded.channels[TARGET],
undefined,
"TARGET evicted by the id tiebreak at equal updatedAt",
);
// Remount in the same second: TARGET's rev high-water is gone with the entry,
// so a fresh click mints rev 1 at updatedAt=NOW.
const click = { version: 1, channels: { [TARGET]: E(true, NOW, 1) } };
// The previously observed remote for TARGET at the same second, rev 100 (it
// may precede the remount — not genuinely concurrent).
const remote = { version: 1, channels: { [TARGET]: E(false, NOW, 100) } };
// equal updatedAt → rev decides → 100 > 1: the click LOSES. Documented
// deterministic residual, proven here through real eviction+remount, not a
// pre-shrunk tuple. Deterministic in both merge orders — a lost click, never
// a divergence.
assert.deepEqual(
merged.channels.c,
mergeStores(click, remote).channels[TARGET],
E(false, NOW, 100),
"equal updatedAt → higher rev wins deterministically (documented residual)",
);
// Deterministic either merge order — a lost click, never a divergence.
assert.deepEqual(mergeStores(remote, click).channels.c, E(false, NOW, 100));
assert.deepEqual(
mergeStores(remote, click).channels[TARGET],
E(false, NOW, 100),
);
});
// ── starredChannelIdsFromStore ────────────────────────────────────────────────

View File

@@ -327,6 +327,69 @@ test("empty-store click survives a later higher-rev head with an older updatedAt
}
});
// Unobserved-future residual (accepted mixed-fleet case, MINOR-2 contrast to the
// fast-clock fix): a click mints at wall-clock t with an empty high-water; a
// genuinely UNOBSERVED opposite-value head then arrives at t+300 and wins on the
// primary updatedAt key. The logical-monotonic stamp only defends against state
// the replica already observed — a future head it never saw before clicking is
// not covered, exactly as under today's shipped LWW.
test("unobserved future head wins over an empty-high-water click on primary updatedAt", async () => {
const { act, cleanup, renderHook } = await import("@testing-library/react");
const { relayClient } = await import("@/shared/api/relayClient");
const { useChannelMutes } = await import("./useChannelMutes.ts");
const live = {};
const restore = stubRelay(relayClient, { live });
const origTauri = window.__TAURI_INTERNALS__;
const origDateNow = Date.now;
Date.now = () => 1000 * 1_000; // click at updatedAt 1000 (t)
window.__TAURI_INTERNALS__ = {
invoke: (cmd) => {
// fetchEvents is stubbed empty, so bootstrap/pre-publish never decrypt;
// the only decrypt is the live head — the genuinely unobserved future
// entry at updatedAt 1300, delivered after the empty-high-water click.
if (cmd === "nip44_decrypt_from_self")
return Promise.resolve(
mutePayload({ shared: { muted: false, updatedAt: 1300, rev: 1 } }),
);
return Promise.reject(new Error(`unmocked ${cmd}`));
},
};
const pubkey = "pk-unobserved-future";
let hook = null;
try {
await act(async () => {
hook = renderHook(() => useChannelMutes(pubkey, "wss://r"));
for (let i = 0; i < 20; i++) await Promise.resolve();
});
await act(async () => hook.result.current.muteChannel("shared")); // empty store → rev 1 @ 1000
await act(async () => {
// Genuinely unobserved head at t+300 (updatedAt 1300), opposite value.
live.cb({
id: "unobserved-future",
pubkey,
created_at: 1300,
content: "cipher",
kind: 30078,
tags: [["d", "channel-mutes"]],
sig: "s",
});
for (let i = 0; i < 20; i++) await Promise.resolve();
});
assert.equal(
hook.result.current.mutedChannelIds.has("shared"),
false,
"unobserved future head wins on primary updatedAt (accepted residual)",
);
hook.unmount();
} finally {
cleanup();
Date.now = origDateNow;
window.__TAURI_INTERNALS__ = origTauri;
restore();
}
});
// Cross-window storage: a peer window's write is observed into the high-water
// and max-merged, so a following click sees the peer's rev and no edit is lost.
test("cross-window storage event is observed and max-merged", async () => {

View File

@@ -327,6 +327,68 @@ test("empty-store click survives a later higher-rev head with an older updatedAt
}
});
// Unobserved-future residual (accepted mixed-fleet case, MINOR-2 contrast to the
// fast-clock fix): a click mints at wall-clock t with an empty high-water; a
// genuinely UNOBSERVED opposite-value head then arrives at t+300 and wins on the
// primary updatedAt key. The logical-monotonic stamp only defends against state
// the replica already observed — a future head it never saw before clicking is
// not covered, exactly as under today's shipped LWW.
test("unobserved future head wins over an empty-high-water click on primary updatedAt", async () => {
const { act, cleanup, renderHook } = await import("@testing-library/react");
const { relayClient } = await import("@/shared/api/relayClient");
const { useChannelStars } = await import("./useChannelStars.ts");
const live = {};
const restore = stubRelay(relayClient, { live });
const origTauri = window.__TAURI_INTERNALS__;
const origDateNow = Date.now;
Date.now = () => 1000 * 1_000; // click at updatedAt 1000 (t)
window.__TAURI_INTERNALS__ = {
invoke: (cmd) => {
// fetchEvents is stubbed empty, so bootstrap/pre-publish never decrypt;
// the only decrypt is the live head — the genuinely unobserved future
// entry at updatedAt 1300, delivered after the empty-high-water click.
if (cmd === "nip44_decrypt_from_self")
return Promise.resolve(
starPayload({ shared: { starred: false, updatedAt: 1300, rev: 1 } }),
);
return Promise.reject(new Error(`unmocked ${cmd}`));
},
};
const pubkey = "pk-unobserved-future";
let hook = null;
try {
await act(async () => {
hook = renderHook(() => useChannelStars(pubkey, "wss://r"));
for (let i = 0; i < 20; i++) await Promise.resolve();
});
await act(async () => hook.result.current.starChannel("shared")); // empty store → rev 1 @ 1000
await act(async () => {
// Genuinely unobserved head at t+300 (updatedAt 1300), opposite value.
live.cb({
id: "unobserved-future",
pubkey,
created_at: 1300,
content: "cipher",
kind: 30078,
tags: [["d", "channel-stars"]],
sig: "s",
});
for (let i = 0; i < 20; i++) await Promise.resolve();
});
assert.equal(
hook.result.current.starredChannelIds.has("shared"),
false,
"unobserved future head wins on primary updatedAt (accepted residual)",
);
hook.unmount();
} finally {
cleanup();
Date.now = origDateNow;
window.__TAURI_INTERNALS__ = origTauri;
restore();
}
});
// Cross-window storage: a peer window's write is observed into the high-water
// and max-merged, so a following click sees the peer's rev and no edit is lost.
test("cross-window storage event is observed and max-merged", async () => {