@@ -173,3 +173,102 @@ def test_transform_state_additional_fields():
173
173
174
174
result = AsyncRealtimePresence ._transform_state (state_with_additional_fields )
175
175
assert result == expected_output
176
+
177
+
178
+ def test_presence_has_callback_attached ():
179
+ """Test that _has_callback_attached property correctly detects presence callbacks."""
180
+ presence = AsyncRealtimePresence ()
181
+
182
+ # Initially no callbacks should be attached
183
+ assert not presence ._has_callback_attached
184
+
185
+ # After setting sync callback
186
+ presence .on_sync (lambda : None )
187
+ assert presence ._has_callback_attached
188
+
189
+ # Reset and test with join callback
190
+ presence = AsyncRealtimePresence ()
191
+ presence .on_join (lambda key , current , new : None )
192
+ assert presence ._has_callback_attached
193
+
194
+ # Reset and test with leave callback
195
+ presence = AsyncRealtimePresence ()
196
+ presence .on_leave (lambda key , current , left : None )
197
+ assert presence ._has_callback_attached
198
+
199
+
200
+ def test_presence_config_includes_enabled_field ():
201
+ """Test that presence config correctly includes enabled flag."""
202
+ from realtime .types import RealtimeChannelPresenceConfig
203
+
204
+ # Test creating presence config with enabled field
205
+ config : RealtimeChannelPresenceConfig = {"key" : "user123" , "enabled" : True }
206
+ assert config ["key" ] == "user123"
207
+ assert config ["enabled" ] == True
208
+
209
+ # Test with enabled False
210
+ config_disabled : RealtimeChannelPresenceConfig = {"key" : "" , "enabled" : False }
211
+ assert config_disabled ["key" ] == ""
212
+ assert config_disabled ["enabled" ] == False
213
+
214
+
215
+ @pytest .mark .asyncio
216
+ async def test_presence_enabled_when_callbacks_attached ():
217
+ """Test that presence.enabled is set correctly based on callback attachment."""
218
+ from unittest .mock import AsyncMock , Mock
219
+
220
+ socket = AsyncRealtimeClient (f"{ URL } /realtime/v1" , ANON_KEY )
221
+ channel = socket .channel ("test" )
222
+
223
+ # Mock the join_push to capture the payload
224
+ mock_join_push = Mock ()
225
+ mock_join_push .receive = Mock (return_value = mock_join_push )
226
+ mock_join_push .update_payload = Mock ()
227
+ mock_join_push .resend = AsyncMock ()
228
+ channel .join_push = mock_join_push
229
+
230
+ # Mock socket connection by setting _ws_connection
231
+ mock_ws = Mock ()
232
+ socket ._ws_connection = mock_ws
233
+ socket ._leave_open_topic = AsyncMock ()
234
+
235
+ # Add presence callback before subscription
236
+ channel .on_presence_sync (lambda : None )
237
+
238
+ await channel .subscribe ()
239
+
240
+ # Verify that update_payload was called
241
+ assert mock_join_push .update_payload .called
242
+
243
+ # Get the payload that was passed to update_payload
244
+ call_args = mock_join_push .update_payload .call_args
245
+ payload = call_args [0 ][0 ]
246
+
247
+ # Verify presence.enabled is True because callback is attached
248
+ assert payload ["config" ]["presence" ]["enabled" ] == True
249
+
250
+
251
+ @pytest .mark .asyncio
252
+ async def test_resubscribe_on_presence_callback_addition ():
253
+ """Test that channel resubscribes when presence callbacks are added after joining."""
254
+ import asyncio
255
+ from unittest .mock import AsyncMock
256
+
257
+ socket = AsyncRealtimeClient (f"{ URL } /realtime/v1" , ANON_KEY )
258
+ channel = socket .channel ("test" )
259
+
260
+ # Mock the channel as joined
261
+ channel .state = "joined"
262
+ channel ._joined_once = True
263
+
264
+ # Mock resubscribe method
265
+ channel ._resubscribe = AsyncMock ()
266
+
267
+ # Add presence callbacks after joining
268
+ channel .on_presence_sync (lambda : None )
269
+
270
+ # Wait a bit for async tasks to complete
271
+ await asyncio .sleep (0.1 )
272
+
273
+ # Verify resubscribe was called
274
+ assert channel ._resubscribe .call_count == 1
0 commit comments