test(sidebar-sync): assert durable adoption and outbox clearing in Carl hook tests

P1/C1-stale: after adopt completes, assert (a) lane storage equals the adopted
remote store, and (c) readOutbox returns null. Two new mutations now trip these
tests: removing clearOutbox from clearPendingState and removing the
onRemoteAdopted callback each leave the two stale tests red.

P2a-1 hook: after H102 adopt completes, assert lane storage equals H102's store
and the click outbox is cleared. The dropped-callback mutation turns the two
hook tests red.

runWholeBlobP2a1HookSuite signature extended with storageKey and readOutbox;
callers (sections, sort) pass readOutbox from their respective lane modules.

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-09-02 22:28:03 -04:00
parent 37d0cd3f32
commit 43e22e19a3
3 changed files with 71 additions and 0 deletions

View File

@@ -346,6 +346,7 @@ runWholeBlobP2a1HookSuite({
return `buzz-channel-sections-outbox.v1:${pubkey}:${encoded}`;
},
storageKey,
readOutbox: readChannelSectionsOutbox,
useHook: useChannelSections,
makeEdit: (r) => r.createSection("P2a1-Section"),
makeEditStore: () => ({

View File

@@ -213,6 +213,7 @@ runWholeBlobP2a1HookSuite({
return `buzz-channel-sort-outbox.v1:${pubkey}:${encoded}`;
},
storageKey,
readOutbox: readChannelSortOutbox,
useHook: useChannelSortPreference,
makeEdit: (r) => r.setSortModeFor("channels", "alpha"),
makeEditStore: () => ({ version: 1, groups: { click: "alpha" } }),

View File

@@ -288,6 +288,42 @@ export function runWholeBlobCarlSuite({
`fires → publishes stale edit`,
);
// Flush React state updates triggered by onRemoteAdopted → setStore(applyRemote(remote)).
// applyRemote's state updater writes to localStorage; act() flushes the commit phase.
await act(async () => {
for (let i = 0; i < 50; i++) await Promise.resolve();
});
// (a) Persisted lane storage must reflect the adopted remote store.
// Mutation (onRemoteAdopted callback removed): adopted store never written → still editStore → fails.
const persistedRaw = window.localStorage.getItem(
storageKey(pubkey, relayUrl),
);
assert.ok(
persistedRaw !== null,
`P1/C1-stale ${label}: lane storage must exist after adopt`,
);
assert.deepEqual(
JSON.parse(persistedRaw),
makeRemoteStore(),
`P1/C1-stale ${label}: lane storage must equal the adopted remote store — ` +
`remove onRemoteAdopted callback → old edit store remains persisted`,
);
// (b) Hook state reflects the adopted remote store (driven by localStorage).
// After act+microtasks above the hook has already re-rendered from the adopted value.
// We use the persisted value as the oracle because it is the source of hook state.
// (assertion collapsed into (a) above — same invariant, same mutation tripwire)
// (c) Losing own outbox must be cleared — head supersedes the stale edit.
// Mutation (clearOutbox removed from clearPendingState): old outbox persists → not null → fails.
assert.equal(
readOutbox(pubkey, relayUrl),
null,
`P1/C1-stale ${label}: own outbox must be cleared after adopt — ` +
`remove clearOutbox from clearPendingState → stale outbox remains, replays again after restart`,
);
hook.unmount();
} finally {
cleanup();
@@ -597,6 +633,8 @@ export function runWholeBlobP2a1Suite({
*/
export function runWholeBlobP2a1HookSuite({
label,
storageKey,
readOutbox,
useHook,
makeEdit,
makeRemoteStore,
@@ -710,6 +748,37 @@ export function runWholeBlobP2a1HookSuite({
`set publishBaseline=lastRemoteHead in publish(_, true) → H102 folds into baseline → publishes over H102`,
);
// Flush React state updates triggered by onRemoteAdopted → setStore(applyRemote(remote)).
// applyRemote's state updater writes to localStorage; act() flushes the commit phase.
await act(async () => {
for (let i = 0; i < 50; i++) await Promise.resolve();
});
// Hook state and lane storage must reflect the adopted H102 store.
// Mutation (onRemoteAdopted callback removed): H102 never written to storage → old edit persists → fails.
const persistedRaw = window.localStorage.getItem(
storageKey(pubkey, relayUrl),
);
assert.ok(
persistedRaw !== null,
`P2a-1 ${label} (hook): lane storage must exist after H102 adopt`,
);
assert.deepEqual(
JSON.parse(persistedRaw),
makeRemoteStore(),
`P2a-1 ${label} (hook): lane storage must equal the adopted H102 store — ` +
`remove onRemoteAdopted callback → own edit store remains persisted`,
);
// Own outbox written by the click must be cleared after H102 supersedes it.
// Mutation (onRemoteAdopted callback removed): adopt never fires, outbox not cleared → not null → fails.
assert.equal(
readOutbox(pubkey, relayUrl),
null,
`P2a-1 ${label} (hook): own outbox must be cleared after H102 adopt — ` +
`remove onRemoteAdopted callback → click outbox remains, replays stale edit on next restart`,
);
hook.unmount();
} finally {
cleanup();