Skip to content
2 changes: 1 addition & 1 deletion packages/react/src/firestore/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { useFirestoreDocument } from "./useFirestoreDocument";
export { useDocumentQuery } from "./useDocumentQuery";
152 changes: 152 additions & 0 deletions packages/react/src/firestore/useDocumentQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { type ReactNode } from "react";
import { describe, expect, test, beforeEach } from "vitest";
import { useDocumentQuery } from "./useDocumentQuery";
import { renderHook, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { doc, setDoc } from "firebase/firestore";

import {
expectFirestoreError,
firestore,
wipeFirestore,
} from "~/testing-utils";

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});

const wrapper = ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

describe("useDocumentQuery", () => {
beforeEach(async () => {
await wipeFirestore();
});

test("it works", async () => {
const ref = doc(firestore, "tests", "useDocumentQuery");

// Set some data
await setDoc(ref, { foo: "bar" });

// Test the hook
const { result } = renderHook(
() =>
useDocumentQuery(ref, {
queryKey: ["some", "doc"],
}),
{ wrapper }
);

// Wait for the query to finish
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

// It shoiuld exist and have data.
expect(result.current.data).toBeDefined();

const snapshot = result.current.data!;
expect(snapshot.exists()).toBe(true);
expect(snapshot.data()?.foo).toBe("bar");
});

test("fetches document from server source", async () => {
const ref = doc(firestore, "tests", "serverSource");

// set data
await setDoc(ref, { foo: "fromServer" });

//test the hook
const { result } = renderHook(
() =>
useDocumentQuery(ref, {
queryKey: ["server", "doc"],
firestore: { source: "server" },
}),
{ wrapper }
);

// await the query
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

// snapshot should exist, data should be fetched from the server and should contain the correct data
const snapshot = result.current.data;
expect(snapshot?.exists()).toBe(true);
expect(snapshot?.data()?.foo).toBe("fromServer");
});

test("handles restricted collections appropriately", async () => {
const ref = doc(firestore, "restrictedCollection", "someDoc");

const { result } = renderHook(
() =>
useDocumentQuery(ref, {
queryKey: ["restricted", "doc"],
}),
{ wrapper }
);

await waitFor(() => {
expect(result.current.isError).toBe(true);
});

expectFirestoreError(result.current.error, "permission-denied");
});

test("returns pending state initially", async () => {
const ref = doc(firestore, "tests", "pendingState");

setDoc(ref, { foo: "pending" });

const { result } = renderHook(
() =>
useDocumentQuery(ref, {
queryKey: ["pending", "state"],
}),
{ wrapper }
);

// initially isPending should be true
expect(result.current.isPending).toBe(true);

// wait for the query to finish, and should have isSuccess true
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

const snapshot = result.current.data;
expect(snapshot?.exists()).toBe(true);
expect(snapshot?.data()?.foo).toBe("pending");
});

test("returns correct data type", async () => {
const ref = doc(firestore, "tests", "typedDoc");

setDoc(ref, { foo: "bar", num: 23 } as { foo: string; num: number });

const { result } = renderHook(
() =>
useDocumentQuery(ref, {
queryKey: ["typed", "doc"],
}),
{ wrapper }
);

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

const snapshot = result.current.data;
expect(snapshot?.exists()).toBe(true);
expect(snapshot?.data()?.foo).toBe("bar");
expect(snapshot?.data()?.num).toBe(23);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type FirestoreUseQueryOptions<TData = unknown, TError = Error> = Omit<
};
};

export function useFirestoreDocument<
export function useDocumentQuery<
FromFirestore extends DocumentData = DocumentData,
ToFirestore extends DocumentData = DocumentData
>(
Expand All @@ -29,7 +29,7 @@ export function useFirestoreDocument<
>
) {
const { firestore, ...queryOptions } = options;

return useQuery<DocumentSnapshot<FromFirestore, ToFirestore>, FirestoreError>(
{
...queryOptions,
Expand Down
54 changes: 0 additions & 54 deletions packages/react/src/firestore/useFirestoreDocument.test.tsx

This file was deleted.

17 changes: 15 additions & 2 deletions vitest/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { type FirebaseApp, initializeApp } from "firebase/app";
import { type FirebaseApp, FirebaseError, initializeApp } from "firebase/app";
import {
getFirestore,
connectFirestoreEmulator,
type Firestore,
} from "firebase/firestore";
import { expect } from "vitest";

const firebaseTestingOptions = {
projectId: "test-project",
Expand Down Expand Up @@ -32,4 +33,16 @@ async function wipeFirestore() {
}
}

export { firestore, wipeFirestore };
function expectFirestoreError(error: unknown, expectedCode: string) {
if (error instanceof FirebaseError) {
expect(error).toBeDefined();
expect(error.code).toBeDefined();
expect(error.code).toBe(expectedCode);
} else {
throw new Error(
"Expected a Firestore error, but received a different type."
);
}
}

export { firestore, wipeFirestore, expectFirestoreError };