Skip to content

Commit 883f4e2

Browse files
test: assert synced data appears in derived collection during pending optimistic mutation (issue #1017)
Adds a test that reproduces the bug where synced data from a source collection does not appear in a derived (live query) collection while there is a pending optimistic mutation. The test: 1. Creates a source collection with controllable sync 2. Creates a derived collection via createLiveQueryCollection 3. Uses createOptimisticAction to insert an item (mutation stays pending) 4. Syncs a new item into the source collection while mutation is pending 5. Asserts that both items should be visible in the derived collection Currently failing because synced data is invisible while optimistic mutations are pending. Co-Authored-By: Claude <[email protected]>
1 parent 9204d76 commit 883f4e2

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

packages/db/tests/query/live-query-collection.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
mockSyncCollectionOptionsNoInitialState,
1616
} from '../utils.js'
1717
import { createDeferred } from '../../src/deferred'
18+
import { createOptimisticAction } from '../../src/optimistic-action.js'
1819
import 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

Comments
 (0)