diff --git a/packages/presence/src/room-registry.test.ts b/packages/presence/src/room-registry.test.ts index 41f7ea96f..fc7609671 100644 --- a/packages/presence/src/room-registry.test.ts +++ b/packages/presence/src/room-registry.test.ts @@ -315,6 +315,44 @@ describe("createPresenceRoomRegistry: doc sync", () => { expect(seen).toEqual([]); }); + + test("a room kept alive only by a doc-update listener survives an otherwise-empty join/leave cycle", () => { + const registry = createPresenceRoomRegistry(); + const docUpdates: string[] = []; + registry.subscribeDocUpdates(docKey, (_update, author) => + docUpdates.push(author), + ); + + registry.join(docKey, state("prn_alice")); + registry.leave(docKey, "prn_alice"); // no presence `subscribe` listener attached + + // The room must still be alive — proven by a doc update still + // reaching the listener registered before the join/leave cycle, + // rather than the room having been silently torn down and recreated + // out from under it. + const alice = clientDoc(); + alice.getText("content").insert(0, "still here"); + registry.applyDocUpdate(docKey, Y.encodeStateAsUpdate(alice), "prn_alice2"); + + expect(docUpdates).toEqual(["prn_alice2"]); + }); + + test("a room kept alive only by a snapshot listener survives an otherwise-empty join/leave cycle", () => { + const registry = createPresenceRoomRegistry(); + const notifications: number[] = []; + registry.subscribeSnapshots(docKey, (info) => + notifications.push(info.version), + ); + + registry.join(docKey, state("prn_alice")); + registry.leave(docKey, "prn_alice"); + + // Proven the same way: the room surviving is what lets a later + // `notifySnapshot` still reach the listener. + registry.notifySnapshot(docKey, { version: 7, savedAt: 1 }); + + expect(notifications).toEqual([7]); + }); }); describe("createPresenceRoomRegistry: deferred destroy", () => { diff --git a/packages/presence/src/room-registry.ts b/packages/presence/src/room-registry.ts index 2775936c1..8af84096b 100644 --- a/packages/presence/src/room-registry.ts +++ b/packages/presence/src/room-registry.ts @@ -277,7 +277,12 @@ export function createPresenceRoomRegistry(): PresenceRoomRegistry { } function isRoomEmpty(room: Room): boolean { - return room.clientIdByPrincipal.size === 0 && room.listeners.size === 0; + return ( + room.clientIdByPrincipal.size === 0 && + room.listeners.size === 0 && + room.docListeners.size === 0 && + room.snapshotListeners.size === 0 + ); } function destroyRoomIfEmpty(key: PresenceRoomKey, room: Room): void { @@ -443,6 +448,7 @@ export function createPresenceRoomRegistry(): PresenceRoomRegistry { room.docListeners.add(listener); return () => { room.docListeners.delete(listener); + destroyRoomIfEmpty(key, room); }; }, @@ -471,6 +477,7 @@ export function createPresenceRoomRegistry(): PresenceRoomRegistry { room.snapshotListeners.add(listener); return () => { room.snapshotListeners.delete(listener); + destroyRoomIfEmpty(key, room); }; }, };