Skip to content

Commit

Permalink
feat(react): add useNamedQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
HassanBahati committed Jan 17, 2025
1 parent 5e6e46a commit 09690bd
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/react/src/firestore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export { useDocumentQuery } from "./useDocumentQuery";
export { useCollectionQuery } from "./useCollectionQuery";
export { useGetAggregateFromServerQuery } from "./useGetAggregateFromServerQuery";
export { useGetCountFromServerQuery } from "./useGetCountFromServerQuery";
// useNamedQuery
export { useNamedQuery } from "./useNamedQuery";
141 changes: 141 additions & 0 deletions packages/react/src/firestore/useNamedQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { describe, expect, test, beforeEach, vi } from "vitest";
import { useNamedQuery } from "./useNamedQuery";
import { renderHook, waitFor } from "@testing-library/react";
import type * as FirestoreTypes from "firebase/firestore";
import { firestore, wipeFirestore } from "~/testing-utils";
import { wrapper, queryClient } from "../../utils";

// Mock the entire firebase/firestore module
vi.mock("firebase/firestore", async () => {
const actual = await vi.importActual<typeof FirestoreTypes>(
"firebase/firestore"
);
return {
collection: actual?.collection,
query: actual?.query,
where: actual?.where,
getFirestore: actual?.getFirestore,
connectFirestoreEmulator: actual?.connectFirestoreEmulator,
namedQuery: vi.fn(),
};
});

// Import after mock definition
import { namedQuery, collection, query, where } from "firebase/firestore";

describe("useNamedQuery", () => {
beforeEach(async () => {
await wipeFirestore();
queryClient.clear();
vi.clearAllMocks();
});

test("returns correct data for an existing named query", async () => {
const mockQuery = query(
collection(firestore, "test"),
where("field", "==", "value")
);
vi.mocked(namedQuery).mockResolvedValue(mockQuery);

const { result } = renderHook(
() =>
useNamedQuery(firestore, "existingQuery", {
queryKey: ["named", "existing"],
}),
{ wrapper }
);

expect(result.current.isLoading).toBe(true);
expect(result.current.data).toBeUndefined();

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
expect(result.current.isSuccess).toBe(true);
});

expect(result.current.data).toBe(mockQuery);
expect(namedQuery).toHaveBeenCalledWith(firestore, "existingQuery");
expect(result.current.error).toBeNull();
});

test("returns null for non-existent named query", async () => {
vi.mocked(namedQuery).mockResolvedValue(null);

const { result } = renderHook(
() =>
useNamedQuery(firestore, "nonExistentQuery", {
queryKey: ["named", "nonexistent"],
}),
{ wrapper }
);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toBeNull();
expect(namedQuery).toHaveBeenCalledWith(firestore, "nonExistentQuery");
expect(result.current.error).toBeNull();
});

test("handles error case properly", async () => {
const mockError = new Error("Query not found");
vi.mocked(namedQuery).mockRejectedValue(mockError);

const { result } = renderHook(
() =>
useNamedQuery(firestore, "errorQuery", {
queryKey: ["named", "error"],
}),
{ wrapper }
);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toBeUndefined();
expect(result.current.error).toBe(mockError);
expect(namedQuery).toHaveBeenCalledWith(firestore, "errorQuery");
});

test("handles query options correctly", async () => {
const mockQuery = query(collection(firestore, "test"));
vi.mocked(namedQuery).mockResolvedValue(mockQuery);

const { result } = renderHook(
() =>
useNamedQuery(firestore, "optionsQuery", {
queryKey: ["named", "options"],
enabled: false,
}),
{ wrapper }
);

expect(result.current.isLoading).toBe(false);
expect(result.current.isFetched).toBe(false);
expect(namedQuery).not.toHaveBeenCalled();
});

test("handles refetching correctly", async () => {
const mockQuery = query(collection(firestore, "test"));
vi.mocked(namedQuery).mockResolvedValue(mockQuery);

const { result } = renderHook(
() =>
useNamedQuery(firestore, "refetchQuery", {
queryKey: ["named", "refetch"],
}),
{ wrapper }
);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

await result.current.refetch();

expect(namedQuery).toHaveBeenCalledTimes(2);
expect(result.current.data).toBe(mockQuery);
});
});
27 changes: 27 additions & 0 deletions packages/react/src/firestore/useNamedQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
import {
type FirestoreError,
type Query,
type DocumentData,
namedQuery,
type Firestore,
} from "firebase/firestore";

type FirestoreUseQueryOptions<TData = unknown, TError = Error> = Omit<
UseQueryOptions<TData, TError>,
"queryFn"
>;

export function useNamedQuery<
AppModelType = DocumentData,
DbModelType extends DocumentData = DocumentData
>(
firestore: Firestore,
name: string,
options: FirestoreUseQueryOptions<Query | null, FirestoreError>
) {
return useQuery<Query | null, FirestoreError>({
...options,
queryFn: () => namedQuery(firestore, name),
});
}

0 comments on commit 09690bd

Please sign in to comment.