From 35175ebe4ceb4dd507b6854a942c72a00c634ade Mon Sep 17 00:00:00 2001 From: HassanBahati Date: Mon, 30 Sep 2024 08:05:09 +0300 Subject: [PATCH 1/2] feat(firestore): add useWaitForPendingWritesQuery --- packages/react/src/firestore/index.ts | 2 +- .../useWaitForPendingWritesQuery.test.tsx | 103 ++++++++++++++++++ .../firestore/useWaitForPendingWritesQuery.ts | 21 ++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx create mode 100644 packages/react/src/firestore/useWaitForPendingWritesQuery.ts diff --git a/packages/react/src/firestore/index.ts b/packages/react/src/firestore/index.ts index 717bdbb9..d732249d 100644 --- a/packages/react/src/firestore/index.ts +++ b/packages/react/src/firestore/index.ts @@ -3,7 +3,7 @@ // useDisableNetworkMutation // useEnableNetworkMutation // useRunTransactionMutation -// useWaitForPendingWritesQuery +export { useWaitForPendingWritesQuery } from "./useWaitForPendingWritesQuery"; // useWriteBatchCommitMutation (WriteBatch) export { useDocumentQuery } from "./useDocumentQuery"; export { useCollectionQuery } from "./useCollectionQuery"; diff --git a/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx b/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx new file mode 100644 index 00000000..61e32212 --- /dev/null +++ b/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx @@ -0,0 +1,103 @@ +import React from "react"; +import { describe, expect, test, beforeEach, vi } from "vitest"; +import { renderHook, act, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { firestore, wipeFirestore } from "~/testing-utils"; +import { useWaitForPendingWritesQuery } from "./useWaitForPendingWritesQuery"; +import { doc, getDoc, setDoc } from "firebase/firestore"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, +}); + +const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} +); + +describe("useWaitForPendingWritesQuery", () => { + beforeEach(async () => { + queryClient.clear(); + await wipeFirestore(); + }); + + test("waits for pending writes to complete successfully", async () => { + const docRef = doc(firestore, "tests", "waitForPendingWritesDoc"); + + // Verify that the document doesn't exist initially + const initialDocSnapshot = await getDoc(docRef); + expect(initialDocSnapshot.exists()).toBe(false); + + const { result } = renderHook( + () => useWaitForPendingWritesQuery(firestore), + { + wrapper, + } + ); + + await setDoc(docRef, { value: "test" }); + + await act(() => result.current.mutate()); + + // Verify that the write has been acknowledged + await waitFor(async () => { + const docSnapshot = await getDoc(docRef); + expect(docSnapshot.exists()).toBe(true); + expect(docSnapshot.data()).toEqual({ value: "test" }); + }); + + expect(result.current.isSuccess).toBe(true); + }); + + test("calls onSuccess callback after mutation", async () => { + const docRef = doc(firestore, "tests", "docOnSuccess"); + const onSuccessMock = vi.fn(); + + const { result } = renderHook( + () => + useWaitForPendingWritesQuery(firestore, { + onSuccess: onSuccessMock, + }), + { + wrapper, + } + ); + + await setDoc(docRef, { value: "success test" }); + + await act(() => result.current.mutate()); + + expect(onSuccessMock).toHaveBeenCalled(); + }); + + test("handles multiple pending writes successfully", async () => { + const docRef1 = doc(firestore, "tests", "docMulti1"); + const docRef2 = doc(firestore, "tests", "docMulti2"); + + const { result } = renderHook( + () => useWaitForPendingWritesQuery(firestore), + { + wrapper, + } + ); + + // Perform multiple Firestore writes + await setDoc(docRef1, { value: "multi write 1" }); + await setDoc(docRef2, { value: "multi write 2" }); + + await act(() => result.current.mutate()); + + await waitFor(async () => { + const doc1Snapshot = await getDoc(docRef1); + const doc2Snapshot = await getDoc(docRef2); + + expect(doc1Snapshot.exists()).toBe(true); + expect(doc1Snapshot.data()).toEqual({ value: "multi write 1" }); + + expect(doc2Snapshot.exists()).toBe(true); + expect(doc2Snapshot.data()).toEqual({ value: "multi write 2" }); + }); + }); +}); diff --git a/packages/react/src/firestore/useWaitForPendingWritesQuery.ts b/packages/react/src/firestore/useWaitForPendingWritesQuery.ts new file mode 100644 index 00000000..85c534af --- /dev/null +++ b/packages/react/src/firestore/useWaitForPendingWritesQuery.ts @@ -0,0 +1,21 @@ +import { useMutation, UseMutationOptions } from "@tanstack/react-query"; +import { + Firestore, + FirestoreError, + waitForPendingWrites, +} from "firebase/firestore"; + +type FirestoreUseMutationOptions = Omit< + UseMutationOptions, + "mutationFn" +>; + +export function useWaitForPendingWritesQuery( + firestore: Firestore, + options?: FirestoreUseMutationOptions +) { + return useMutation({ + ...options, + mutationFn: () => waitForPendingWrites(firestore), + }); +} From 6a7ba5ccd8e2298711b51581feedd92078f0741f Mon Sep 17 00:00:00 2001 From: HassanBahati Date: Tue, 1 Oct 2024 13:08:26 +0300 Subject: [PATCH 2/2] refactor(useWaitForPendingWritesQuery): change from using a mutateFn to a queryFn --- .../useWaitForPendingWritesQuery.test.tsx | 83 +++---------------- .../firestore/useWaitForPendingWritesQuery.ts | 18 ++-- 2 files changed, 22 insertions(+), 79 deletions(-) diff --git a/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx b/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx index 61e32212..c842d893 100644 --- a/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx +++ b/packages/react/src/firestore/useWaitForPendingWritesQuery.test.tsx @@ -2,9 +2,13 @@ import React from "react"; import { describe, expect, test, beforeEach, vi } from "vitest"; import { renderHook, act, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { firestore, wipeFirestore } from "~/testing-utils"; +import { + expectFirestoreError, + firestore, + wipeFirestore, +} from "~/testing-utils"; import { useWaitForPendingWritesQuery } from "./useWaitForPendingWritesQuery"; -import { doc, getDoc, setDoc } from "firebase/firestore"; +import { doc, setDoc } from "firebase/firestore"; const queryClient = new QueryClient({ defaultOptions: { @@ -23,81 +27,20 @@ describe("useWaitForPendingWritesQuery", () => { await wipeFirestore(); }); - test("waits for pending writes to complete successfully", async () => { - const docRef = doc(firestore, "tests", "waitForPendingWritesDoc"); - - // Verify that the document doesn't exist initially - const initialDocSnapshot = await getDoc(docRef); - expect(initialDocSnapshot.exists()).toBe(false); - - const { result } = renderHook( - () => useWaitForPendingWritesQuery(firestore), - { - wrapper, - } - ); - - await setDoc(docRef, { value: "test" }); - - await act(() => result.current.mutate()); - - // Verify that the write has been acknowledged - await waitFor(async () => { - const docSnapshot = await getDoc(docRef); - expect(docSnapshot.exists()).toBe(true); - expect(docSnapshot.data()).toEqual({ value: "test" }); - }); - - expect(result.current.isSuccess).toBe(true); - }); - - test("calls onSuccess callback after mutation", async () => { - const docRef = doc(firestore, "tests", "docOnSuccess"); - const onSuccessMock = vi.fn(); + test("enters loading state when pending writes are in progress", async () => { + const docRef = doc(firestore, "tests", "loadingStateDoc"); const { result } = renderHook( () => useWaitForPendingWritesQuery(firestore, { - onSuccess: onSuccessMock, + queryKey: ["pending", "write", "loading"], }), - { - wrapper, - } + { wrapper } ); - await setDoc(docRef, { value: "success test" }); - - await act(() => result.current.mutate()); - - expect(onSuccessMock).toHaveBeenCalled(); - }); - - test("handles multiple pending writes successfully", async () => { - const docRef1 = doc(firestore, "tests", "docMulti1"); - const docRef2 = doc(firestore, "tests", "docMulti2"); - - const { result } = renderHook( - () => useWaitForPendingWritesQuery(firestore), - { - wrapper, - } - ); - - // Perform multiple Firestore writes - await setDoc(docRef1, { value: "multi write 1" }); - await setDoc(docRef2, { value: "multi write 2" }); - - await act(() => result.current.mutate()); - - await waitFor(async () => { - const doc1Snapshot = await getDoc(docRef1); - const doc2Snapshot = await getDoc(docRef2); - - expect(doc1Snapshot.exists()).toBe(true); - expect(doc1Snapshot.data()).toEqual({ value: "multi write 1" }); + // Initiate a write without an await + setDoc(docRef, { value: "loading-test" }); - expect(doc2Snapshot.exists()).toBe(true); - expect(doc2Snapshot.data()).toEqual({ value: "multi write 2" }); - }); + expect(result.current.isPending).toBe(true); }); }); diff --git a/packages/react/src/firestore/useWaitForPendingWritesQuery.ts b/packages/react/src/firestore/useWaitForPendingWritesQuery.ts index 85c534af..f8c47678 100644 --- a/packages/react/src/firestore/useWaitForPendingWritesQuery.ts +++ b/packages/react/src/firestore/useWaitForPendingWritesQuery.ts @@ -1,21 +1,21 @@ -import { useMutation, UseMutationOptions } from "@tanstack/react-query"; +import { useQuery, type UseQueryOptions } from "@tanstack/react-query"; import { - Firestore, - FirestoreError, + type Firestore, + type FirestoreError, waitForPendingWrites, } from "firebase/firestore"; -type FirestoreUseMutationOptions = Omit< - UseMutationOptions, - "mutationFn" +type FirestoreUseQueryOptions = Omit< + UseQueryOptions, + "queryFn" >; export function useWaitForPendingWritesQuery( firestore: Firestore, - options?: FirestoreUseMutationOptions + options: FirestoreUseQueryOptions ) { - return useMutation({ + return useQuery({ ...options, - mutationFn: () => waitForPendingWrites(firestore), + queryFn: () => waitForPendingWrites(firestore), }); }