@@ -1250,13 +1250,14 @@ describe(`Collection with schema validation`, () => {
12501250 } )
12511251
12521252 it ( `should not block user actions when keys are recently synced` , async ( ) => {
1253- // This test reproduces the issue where rapid user actions get blocked
1253+ // This test reproduces the ACTUAL issue where rapid user actions get blocked
12541254 // when optimistic updates back up with slow sync responses
12551255 const txResolvers : Array < ( ) => void > = [ ]
12561256 const emitter = mitt ( )
1257+ const changeEvents : Array < any > = [ ]
12571258
12581259 const mutationFn = vi . fn ( ) . mockImplementation ( async ( { transaction } ) => {
1259- // Simulate server operation that can be controlled
1260+ // Simulate SLOW server operation - this is key to reproducing the issue
12601261 return new Promise ( ( resolve ) => {
12611262 txResolvers . push ( ( ) => {
12621263 emitter . emit ( `sync` , transaction . mutations )
@@ -1282,7 +1283,7 @@ describe(`Collection with schema validation`, () => {
12821283 commit ( )
12831284 markReady ( )
12841285
1285- // Listen for sync events
1286+ // Listen for sync events - this triggers the problematic batching
12861287 // @ts -expect-error don't trust mitt's typing
12871288 emitter . on ( `*` , ( _ , changes : Array < PendingMutation > ) => {
12881289 begin ( )
@@ -1300,36 +1301,67 @@ describe(`Collection with schema validation`, () => {
13001301 onUpdate : mutationFn ,
13011302 } )
13021303
1304+ // Listen to change events to verify they're emitted (this was the actual problem)
1305+ collection . subscribeChanges ( ( changes ) => {
1306+ changeEvents . push ( ...changes )
1307+ } )
1308+
13031309 await collection . stateWhenReady ( )
13041310
1305- // Step 1: First user action - should work fine
1311+ // CRITICAL: Simulate rapid clicking WITHOUT waiting for transactions to complete
1312+ // This is what actually triggers the bug - multiple pending transactions
1313+
1314+ // Step 1: First click
13061315 const tx1 = collection . update ( 1 , ( draft ) => {
13071316 draft . checked = true
13081317 } )
13091318 expect ( collection . state . get ( 1 ) ?. checked ) . toBe ( true )
1310- expect ( collection . optimisticUpserts . has ( 1 ) ) . toBe ( true )
1319+ const initialEventCount = changeEvents . length
1320+
1321+ // Step 2: Second click immediately (before first completes)
1322+ const tx2 = collection . update ( 1 , ( draft ) => {
1323+ draft . checked = false
1324+ } )
1325+ expect ( collection . state . get ( 1 ) ?. checked ) . toBe ( false )
1326+
1327+ // Step 3: Third click immediately (before others complete)
1328+ const tx3 = collection . update ( 1 , ( draft ) => {
1329+ draft . checked = true
1330+ } )
1331+ expect ( collection . state . get ( 1 ) ?. checked ) . toBe ( true )
1332+
1333+ // CRITICAL TEST: Verify events are still being emitted for rapid user actions
1334+ // Before the fix, these would be batched and UI would freeze
1335+ expect ( changeEvents . length ) . toBeGreaterThan ( initialEventCount )
1336+ expect ( mutationFn ) . toHaveBeenCalledTimes ( 3 )
13111337
1312- // Step 2: Complete the transaction to trigger sync
1338+ // Now complete the first transaction to trigger sync and batching
13131339 txResolvers [ 0 ] ?.( )
13141340 await tx1 . isPersisted . promise
13151341
1316- // At this point, key 1 should be in recentlySyncedKeys due to the sync event
1342+ // Step 4: More rapid clicks after sync starts (this is where the bug occurred)
1343+ const eventCountBeforeRapidClicks = changeEvents . length
13171344
1318- // Step 3: Try another user action on the same key immediately
1319- // This should NOT be blocked (this was the bug)
1320- const tx2 = collection . update ( 1 , ( draft ) => {
1345+ const tx4 = collection . update ( 1 , ( draft ) => {
13211346 draft . checked = false
13221347 } )
1348+ const tx5 = collection . update ( 1 , ( draft ) => {
1349+ draft . checked = true
1350+ } )
13231351
1324- // The optimistic state should be updated
1325- expect ( collection . state . get ( 1 ) ?. checked ) . toBe ( false )
1326- expect ( collection . optimisticUpserts . has ( 1 ) ) . toBe ( true )
1327-
1328- // The mutation function should have been called
1329- expect ( mutationFn ) . toHaveBeenCalledTimes ( 2 )
1352+ // CRITICAL: Verify that even after sync/batching starts, user actions still emit events
1353+ expect ( changeEvents . length ) . toBeGreaterThan ( eventCountBeforeRapidClicks )
1354+ expect ( collection . state . get ( 1 ) ?. checked ) . toBe ( true ) // Last action should win
13301355
1331- // Clean up
1332- txResolvers [ 1 ] ?.( )
1333- await tx2 . isPersisted . promise
1356+ // Clean up remaining transactions
1357+ for ( let i = 1 ; i < txResolvers . length ; i ++ ) {
1358+ txResolvers [ i ] ?.( )
1359+ }
1360+ await Promise . all ( [
1361+ tx2 . isPersisted . promise ,
1362+ tx3 . isPersisted . promise ,
1363+ tx4 . isPersisted . promise ,
1364+ tx5 . isPersisted . promise ,
1365+ ] )
13341366 } )
13351367} )
0 commit comments