From 883f4e23c86e1a582f7e3df53d9130974ea31ab9 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:19:32 +0000 Subject: [PATCH] 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 --- .../tests/query/live-query-collection.test.ts | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/packages/db/tests/query/live-query-collection.test.ts b/packages/db/tests/query/live-query-collection.test.ts index 58ed050ea..66f969f92 100644 --- a/packages/db/tests/query/live-query-collection.test.ts +++ b/packages/db/tests/query/live-query-collection.test.ts @@ -15,6 +15,7 @@ import { mockSyncCollectionOptionsNoInitialState, } from '../utils.js' import { createDeferred } from '../../src/deferred' +import { createOptimisticAction } from '../../src/optimistic-action.js' import type { ChangeMessage, LoadSubsetOptions } from '../../src/types.js' // Sample user type for tests @@ -1072,6 +1073,111 @@ describe(`createLiveQueryCollection`, () => { expect(liveTodo?.completed).toBe(true) }) }) + + describe(`synced data visibility during pending optimistic mutations`, () => { + it(`shows synced data in derived collection while optimistic action mutation is pending`, async () => { + // This test verifies that synced data from the source collection appears + // in the derived live query collection even when there is a pending + // optimistic mutation on that derived collection. + // + // Uses createOptimisticAction with a controlled mutationFn to ensure + // the optimistic mutation stays pending while we sync new data. + + type Item = { id: string; value: string } + + let syncBegin!: () => void + let syncWrite!: ( + change: Omit, `key`>, + ) => void + let syncCommit!: () => void + let syncMarkReady!: () => void + + // Create source collection with controllable sync + const source = createCollection({ + id: `source-for-optimistic-action`, + getKey: (item) => item.id, + startSync: true, + sync: { + sync: ({ begin, write, commit, markReady }) => { + syncBegin = begin + syncWrite = write + syncCommit = commit + syncMarkReady = markReady + }, + }, + }) + + // Mark source ready (no initial data) + syncMarkReady() + + // Create derived collection (simple passthrough) + const derived = createLiveQueryCollection((q) => + q.from({ item: source }), + ) + + await derived.preload() + + // Verify derived collection is initially empty + expect(derived.size).toBe(0) + + // Create a deferred promise to control when the optimistic mutation resolves + let resolveOptimistic!: () => void + const optimisticPromise = new Promise((resolve) => { + resolveOptimistic = resolve + }) + + // Use createOptimisticAction to insert an item with a pending mutation + const optimisticInsert = createOptimisticAction({ + onMutate: (item) => { + source.insert(item) + }, + mutationFn: async () => { + // Keep the mutation pending until we explicitly resolve + await optimisticPromise + }, + }) + + // Execute the optimistic action - mutation will be PENDING + const transaction = optimisticInsert({ + id: `optimistic-1`, + value: `optimistic`, + }) + + // Wait for the optimistic insert to be visible + await new Promise((resolve) => setTimeout(resolve, 10)) + + // The optimistic item should be visible in the derived collection + expect(derived.size).toBe(1) + expect(derived.has(`optimistic-1`)).toBe(true) + + // Verify the transaction is still pending + expect(transaction.state).toBe(`persisting`) + + // Sync a NEW item into the source collection while the optimistic mutation is pending + syncBegin() + syncWrite({ + type: `insert`, + value: { id: `synced-1`, value: `synced` }, + }) + syncCommit() + + // Wait for the sync to propagate to the derived collection + await new Promise((resolve) => setTimeout(resolve, 10)) + + // KEY ASSERTIONS: Both items should be visible while the mutation is pending + // Synced data should appear in derived collections regardless of pending optimistic state + expect(derived.has(`optimistic-1`)).toBe(true) + expect(derived.has(`synced-1`)).toBe(true) + expect(derived.size).toBe(2) + + // Now resolve the optimistic mutation + resolveOptimistic() + await transaction.isPersisted.promise + + // After resolution, the synced item should still be visible + expect(derived.has(`synced-1`)).toBe(true) + }) + }) }) describe(`isLoadingSubset integration`, () => {