Skip to content

Commit 89c29a5

Browse files
committed
Add a spec for useFindFirst
1 parent ddc7ba1 commit 89c29a5

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import type { GadgetRecord } from "@gadgetinc/api-client-core";
2+
import { renderHook } from "@testing-library/react";
3+
import type { IsExact } from "conditional-type-checks";
4+
import { assert } from "conditional-type-checks";
5+
import { useFindFirst } from "../src/index.js";
6+
import type { ErrorWrapper } from "../src/utils.js";
7+
import { relatedProductsApi } from "./apis.js";
8+
import { MockClientWrapper, mockUrqlClient } from "./testWrappers.js";
9+
10+
describe("useFindFirst", () => {
11+
// these functions are typechecked but never run to avoid actually making API calls
12+
const _TestFindFirstReturnsTypedDataWithExplicitSelection = () => {
13+
const [{ data, fetching, error }, refresh] = useFindFirst(relatedProductsApi.user, {
14+
filter: { email: { equals: "[email protected]" } },
15+
select: { id: true, email: true },
16+
});
17+
18+
assert<IsExact<typeof fetching, boolean>>(true);
19+
assert<IsExact<typeof data, undefined | GadgetRecord<{ id: string; email: string | null }>>>(true);
20+
assert<IsExact<typeof error, ErrorWrapper | undefined>>(true);
21+
22+
// data is accessible via dot access
23+
if (data) {
24+
data.id;
25+
data.email;
26+
}
27+
28+
// hook return value includes the urql refresh function
29+
refresh();
30+
};
31+
32+
const _TestFindFirstReturnsTypedDataWithNoSelection = () => {
33+
const [{ data }] = useFindFirst(relatedProductsApi.user);
34+
35+
if (data) {
36+
data.id;
37+
data.email;
38+
}
39+
};
40+
41+
test("it can find the first record", async () => {
42+
const { result } = renderHook(() => useFindFirst(relatedProductsApi.user), {
43+
wrapper: MockClientWrapper(relatedProductsApi),
44+
});
45+
46+
expect(result.current[0].data).toBeFalsy();
47+
expect(result.current[0].fetching).toBe(true);
48+
expect(result.current[0].error).toBeFalsy();
49+
50+
expect(mockUrqlClient.executeQuery).toBeCalledTimes(1);
51+
52+
mockUrqlClient.executeQuery.pushResponse("users", {
53+
data: {
54+
users: {
55+
edges: [{ cursor: "123", node: { id: "123", email: "[email protected]" } }],
56+
pageInfo: {
57+
startCursor: "123",
58+
endCursor: "123",
59+
hasNextPage: false,
60+
hasPreviousPage: false,
61+
},
62+
},
63+
},
64+
stale: false,
65+
hasNext: false,
66+
});
67+
68+
expect(result.current[0].data!.id).toEqual("123");
69+
expect(result.current[0].data!.email).toEqual("[email protected]");
70+
expect(result.current[0].fetching).toBe(false);
71+
expect(result.current[0].error).toBeFalsy();
72+
});
73+
74+
test("it return null if the record with the given field value can't be found", async () => {
75+
const { result } = renderHook(() => useFindFirst(relatedProductsApi.user), {
76+
wrapper: MockClientWrapper(relatedProductsApi),
77+
});
78+
79+
expect(result.current[0].data).toBeFalsy();
80+
expect(result.current[0].fetching).toBe(true);
81+
expect(result.current[0].error).toBeFalsy();
82+
83+
expect(mockUrqlClient.executeQuery).toBeCalledTimes(1);
84+
85+
mockUrqlClient.executeQuery.pushResponse("users", {
86+
data: {
87+
users: {
88+
edges: [],
89+
pageInfo: {
90+
startCursor: null,
91+
endCursor: null,
92+
hasNextPage: false,
93+
hasPreviousPage: false,
94+
},
95+
},
96+
},
97+
stale: false,
98+
hasNext: false,
99+
});
100+
101+
expect(result.current[0].data).toBeFalsy();
102+
expect(result.current[0].fetching).toBe(false);
103+
expect(result.current[0].error).toBeUndefined();
104+
});
105+
106+
test("returns the same data object on rerender", async () => {
107+
const { result, rerender } = renderHook(() => useFindFirst(relatedProductsApi.user), {
108+
wrapper: MockClientWrapper(relatedProductsApi),
109+
});
110+
111+
expect(mockUrqlClient.executeQuery).toBeCalledTimes(1);
112+
113+
mockUrqlClient.executeQuery.pushResponse("users", {
114+
data: {
115+
users: {
116+
edges: [{ cursor: "123", node: { id: "123", email: "[email protected]" } }],
117+
pageInfo: {
118+
startCursor: "123",
119+
endCursor: "123",
120+
hasNextPage: false,
121+
hasPreviousPage: false,
122+
},
123+
},
124+
},
125+
stale: false,
126+
hasNext: false,
127+
});
128+
129+
const data = result.current[0].data;
130+
expect(data).toBeTruthy();
131+
132+
rerender();
133+
134+
expect(result.current[0].data).toBe(data);
135+
});
136+
137+
test("it can suspend when finding data", async () => {
138+
const { result, rerender } = renderHook(() => useFindFirst(relatedProductsApi.user, { suspense: true }), {
139+
wrapper: MockClientWrapper(relatedProductsApi),
140+
});
141+
142+
// first render never completes as the component suspends
143+
expect(result.current).toBeFalsy();
144+
expect(mockUrqlClient.executeQuery).toBeCalledTimes(1);
145+
146+
mockUrqlClient.executeQuery.pushResponse("users", {
147+
data: {
148+
users: {
149+
edges: [{ cursor: "123", node: { id: "123", email: "[email protected]" } }],
150+
pageInfo: {
151+
startCursor: "123",
152+
endCursor: "123",
153+
hasNextPage: false,
154+
hasPreviousPage: false,
155+
},
156+
},
157+
},
158+
stale: false,
159+
hasNext: false,
160+
});
161+
162+
// rerender as react would do when the suspense promise resolves
163+
rerender();
164+
expect(result.current).toBeTruthy();
165+
expect(result.current[0].data!.id).toEqual("123");
166+
expect(result.current[0].data!.email).toEqual("[email protected]");
167+
expect(result.current[0].error).toBeFalsy();
168+
169+
const beforeObject = result.current[0];
170+
rerender();
171+
expect(result.current[0]).toBe(beforeObject);
172+
});
173+
});

0 commit comments

Comments
 (0)