@@ -15,6 +15,7 @@ import {
1515 mockSyncCollectionOptionsNoInitialState ,
1616} from '../utils.js'
1717import { createDeferred } from '../../src/deferred'
18+ import { createOptimisticAction } from '../../src/optimistic-action.js'
1819import type { ChangeMessage , LoadSubsetOptions } from '../../src/types.js'
1920
2021// Sample user type for tests
@@ -1072,6 +1073,111 @@ describe(`createLiveQueryCollection`, () => {
10721073 expect ( liveTodo ?. completed ) . toBe ( true )
10731074 } )
10741075 } )
1076+
1077+ describe ( `synced data visibility during pending optimistic mutations` , ( ) => {
1078+ it ( `shows synced data in derived collection while optimistic action mutation is pending` , async ( ) => {
1079+ // This test verifies that synced data from the source collection appears
1080+ // in the derived live query collection even when there is a pending
1081+ // optimistic mutation on that derived collection.
1082+ //
1083+ // Uses createOptimisticAction with a controlled mutationFn to ensure
1084+ // the optimistic mutation stays pending while we sync new data.
1085+
1086+ type Item = { id : string ; value : string }
1087+
1088+ let syncBegin ! : ( ) => void
1089+ let syncWrite ! : (
1090+ change : Omit < ChangeMessage < Item , string | number > , `key`> ,
1091+ ) => void
1092+ let syncCommit ! : ( ) => void
1093+ let syncMarkReady ! : ( ) => void
1094+
1095+ // Create source collection with controllable sync
1096+ const source = createCollection < Item > ( {
1097+ id : `source-for-optimistic-action` ,
1098+ getKey : ( item ) => item . id ,
1099+ startSync : true ,
1100+ sync : {
1101+ sync : ( { begin, write, commit, markReady } ) => {
1102+ syncBegin = begin
1103+ syncWrite = write
1104+ syncCommit = commit
1105+ syncMarkReady = markReady
1106+ } ,
1107+ } ,
1108+ } )
1109+
1110+ // Mark source ready (no initial data)
1111+ syncMarkReady ( )
1112+
1113+ // Create derived collection (simple passthrough)
1114+ const derived = createLiveQueryCollection ( ( q ) =>
1115+ q . from ( { item : source } ) ,
1116+ )
1117+
1118+ await derived . preload ( )
1119+
1120+ // Verify derived collection is initially empty
1121+ expect ( derived . size ) . toBe ( 0 )
1122+
1123+ // Create a deferred promise to control when the optimistic mutation resolves
1124+ let resolveOptimistic ! : ( ) => void
1125+ const optimisticPromise = new Promise < void > ( ( resolve ) => {
1126+ resolveOptimistic = resolve
1127+ } )
1128+
1129+ // Use createOptimisticAction to insert an item with a pending mutation
1130+ const optimisticInsert = createOptimisticAction < Item > ( {
1131+ onMutate : ( item ) => {
1132+ source . insert ( item )
1133+ } ,
1134+ mutationFn : async ( ) => {
1135+ // Keep the mutation pending until we explicitly resolve
1136+ await optimisticPromise
1137+ } ,
1138+ } )
1139+
1140+ // Execute the optimistic action - mutation will be PENDING
1141+ const transaction = optimisticInsert ( {
1142+ id : `optimistic-1` ,
1143+ value : `optimistic` ,
1144+ } )
1145+
1146+ // Wait for the optimistic insert to be visible
1147+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
1148+
1149+ // The optimistic item should be visible in the derived collection
1150+ expect ( derived . size ) . toBe ( 1 )
1151+ expect ( derived . has ( `optimistic-1` ) ) . toBe ( true )
1152+
1153+ // Verify the transaction is still pending
1154+ expect ( transaction . state ) . toBe ( `persisting` )
1155+
1156+ // Sync a NEW item into the source collection while the optimistic mutation is pending
1157+ syncBegin ( )
1158+ syncWrite ( {
1159+ type : `insert` ,
1160+ value : { id : `synced-1` , value : `synced` } ,
1161+ } )
1162+ syncCommit ( )
1163+
1164+ // Wait for the sync to propagate to the derived collection
1165+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
1166+
1167+ // KEY ASSERTIONS: Both items should be visible while the mutation is pending
1168+ // Synced data should appear in derived collections regardless of pending optimistic state
1169+ expect ( derived . has ( `optimistic-1` ) ) . toBe ( true )
1170+ expect ( derived . has ( `synced-1` ) ) . toBe ( true )
1171+ expect ( derived . size ) . toBe ( 2 )
1172+
1173+ // Now resolve the optimistic mutation
1174+ resolveOptimistic ( )
1175+ await transaction . isPersisted . promise
1176+
1177+ // After resolution, the synced item should still be visible
1178+ expect ( derived . has ( `synced-1` ) ) . toBe ( true )
1179+ } )
1180+ } )
10751181 } )
10761182
10771183 describe ( `isLoadingSubset integration` , ( ) => {
0 commit comments