Skip to content

Commit 70ba65d

Browse files
committed
Extract helper to own file
1 parent 7027232 commit 70ba65d

3 files changed

Lines changed: 136 additions & 120 deletions

File tree

config/jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const reactSharedTestFileIgnoreList = [
4242
ignoreDTSFiles,
4343
ignoreTSFiles,
4444
"src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx",
45+
"src/react/hooks/__tests__/useLoadableQuery/testUtils.tsx",
4546
"src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx",
4647
"src/react/query-preloader/__tests__/createQueryPreloader/testUtils.tsx",
4748
];

src/react/hooks/__tests__/useLoadableQuery/customScalars.test.tsx

Lines changed: 10 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { RenderOptions } from "@testing-library/react";
21
import { screen } from "@testing-library/react";
32
import {
43
createRenderStream,
@@ -7,11 +6,9 @@ import {
76
} from "@testing-library/react-render-stream";
87
import { userEvent } from "@testing-library/user-event";
98
import React, { Suspense } from "react";
10-
import { flushSync } from "react-dom";
11-
import { ErrorBoundary } from "react-error-boundary";
129
import { delay, of } from "rxjs";
1310

14-
import type { DataState, ErrorLike, OperationVariables } from "@apollo/client";
11+
import type { OperationVariables } from "@apollo/client";
1512
import {
1613
ApolloClient,
1714
ApolloLink,
@@ -36,114 +33,7 @@ import {
3633
} from "@apollo/client/testing/internal";
3734
import { invariant } from "@apollo/client/utilities/invariant";
3835

39-
async function renderHook<
40-
TData,
41-
TVariables extends OperationVariables,
42-
TStates extends DataState<TData>["dataState"] = DataState<TData>["dataState"],
43-
Props = never,
44-
>(
45-
renderHookImpl: (
46-
props: Props extends never ? undefined : Props
47-
) => useLoadableQuery.Result<TData, TVariables, TStates>,
48-
options: Pick<RenderOptions, "wrapper"> & { initialProps?: Props }
49-
) {
50-
function UseReadQuery({
51-
queryRef,
52-
}: {
53-
queryRef: QueryRef<TData, TVariables, TStates>;
54-
}) {
55-
useTrackRenders({ name: "useReadQuery" });
56-
mergeSnapshot({ result: useReadQuery(queryRef) });
57-
58-
return null;
59-
}
60-
61-
function SuspenseFallback() {
62-
useTrackRenders({ name: "<Suspense />" });
63-
64-
return null;
65-
}
66-
67-
function ErrorFallback() {
68-
useTrackRenders({ name: "<ErrorBoundary />" });
69-
70-
return null;
71-
}
72-
73-
type RefetchFunction = useLoadableQuery.Handlers<
74-
TData,
75-
TVariables
76-
>["refetch"];
77-
78-
function App({ props }: { props: Props | undefined }) {
79-
useTrackRenders({ name: "useLoadableQuery" });
80-
const [loadQuery, queryRef, { refetch }] = renderHookImpl(props as any);
81-
82-
mergeSnapshot({ loadQuery, refetch });
83-
84-
return (
85-
<Suspense fallback={<SuspenseFallback />}>
86-
<ErrorBoundary
87-
FallbackComponent={ErrorFallback}
88-
onError={(error) => replaceSnapshot({ error })}
89-
>
90-
{queryRef && <UseReadQuery queryRef={queryRef} />}
91-
</ErrorBoundary>
92-
</Suspense>
93-
);
94-
}
95-
96-
const {
97-
render,
98-
getCurrentRender,
99-
takeRender,
100-
mergeSnapshot,
101-
replaceSnapshot,
102-
} = createRenderStream<
103-
| {
104-
loadQuery: useLoadableQuery.LoadQueryFunction<TVariables>;
105-
refetch: RefetchFunction;
106-
result?: useReadQuery.Result<TData, TStates>;
107-
}
108-
| { error: ErrorLike }
109-
>({
110-
// These values should always be available, but createRenderStream needs an
111-
// initial snapshot when using mergeSnapshot so we provide it with something
112-
initialSnapshot: { loadQuery: null as any, refetch: null as any },
113-
});
114-
115-
const utils = await render(<App props={options.initialProps} />, options);
116-
117-
function rerender(props: Props) {
118-
return utils.rerender(<App props={props} />);
119-
}
120-
121-
function getCurrentSnapshot() {
122-
const { snapshot } = getCurrentRender();
123-
invariant(
124-
"loadQuery" in snapshot,
125-
"Expected rendered hook instead of error boundary"
126-
);
127-
128-
return snapshot;
129-
}
130-
131-
// React 18 skips committing the suspense fallback when loadQuery/refetch is
132-
// triggered as a default-priority update, so the fallback render is missed
133-
// and tests may fail. flushSync forces a synchronous commit so the fallback
134-
// renders in both React 18 and 19, while returning the underlying result.
135-
const loadQuery: useLoadableQuery.LoadQueryFunction<TVariables> = (
136-
...args
137-
) => {
138-
return flushSync(() => getCurrentSnapshot().loadQuery(...args));
139-
};
140-
141-
const refetch: RefetchFunction = (...args) => {
142-
return flushSync(() => getCurrentSnapshot().refetch(...args));
143-
};
144-
145-
return { takeRender, rerender, getCurrentSnapshot, refetch, loadQuery };
146-
}
36+
import { renderUseLoadableQueryHook } from "./testUtils.js";
14737

14838
test("serializes scalar variables used in field arguments", async () => {
14939
let requestVariables!: OperationVariables;
@@ -478,7 +368,7 @@ test("preserves referential identity when refetching identical serialized scalar
478368
});
479369

480370
using _disabledAct = disableActEnvironment();
481-
const { takeRender, refetch, loadQuery } = await renderHook(
371+
const { takeRender, refetch, loadQuery } = await renderUseLoadableQueryHook(
482372
() => useLoadableQuery(query),
483373
{ wrapper: createClientWrapper(client) }
484374
);
@@ -609,7 +499,7 @@ test("serializes scalar fields in the error with a `none` error policy", async (
609499
});
610500

611501
using _disabledAct = disableActEnvironment();
612-
const { takeRender, loadQuery } = await renderHook(
502+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
613503
() => useLoadableQuery(query, { errorPolicy: "none" }),
614504
{ wrapper: createClientWrapper(client) }
615505
);
@@ -701,7 +591,7 @@ test("parses scalar fields in the result and serializes them in the error with a
701591
});
702592

703593
using _disabledAct = disableActEnvironment();
704-
const { takeRender, loadQuery } = await renderHook(
594+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
705595
() => useLoadableQuery(query, { errorPolicy: "all" }),
706596
{ wrapper: createClientWrapper(client) }
707597
);
@@ -805,7 +695,7 @@ test("parses custom scalar fields with an `ignore` error policy", async () => {
805695
});
806696

807697
using _disabledAct = disableActEnvironment();
808-
const { takeRender, loadQuery } = await renderHook(
698+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
809699
() => useLoadableQuery(query, { errorPolicy: "ignore" }),
810700
{ wrapper: createClientWrapper(client) }
811701
);
@@ -880,7 +770,7 @@ test("parses custom scalar fields across `@defer` payloads (defer20220824)", asy
880770
`;
881771

882772
using _disabledAct = disableActEnvironment();
883-
const { takeRender, loadQuery } = await renderHook(
773+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
884774
() => useLoadableQuery(query),
885775
{ wrapper: createClientWrapper(client) }
886776
);
@@ -990,7 +880,7 @@ test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", a
990880
`;
991881

992882
using _disabledAct = disableActEnvironment();
993-
const { takeRender, loadQuery } = await renderHook(
883+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
994884
() => useLoadableQuery(query),
995885
{ wrapper: createClientWrapper(client) }
996886
);
@@ -1098,7 +988,7 @@ test("parses custom scalar fields across `@stream` payloads (defer20220824)", as
1098988
`;
1099989

1100990
using _disabledAct = disableActEnvironment();
1101-
const { takeRender, loadQuery } = await renderHook(
991+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
1102992
() => useLoadableQuery(query),
1103993
{ wrapper: createClientWrapper(client) }
1104994
);
@@ -1225,7 +1115,7 @@ test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)",
12251115
`;
12261116

12271117
using _disabledAct = disableActEnvironment();
1228-
const { takeRender, loadQuery } = await renderHook(
1118+
const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
12291119
() => useLoadableQuery(query),
12301120
{ wrapper: createClientWrapper(client) }
12311121
);
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { RenderOptions } from "@testing-library/react";
2+
import {
3+
createRenderStream,
4+
useTrackRenders,
5+
} from "@testing-library/react-render-stream";
6+
import React, { Suspense } from "react";
7+
import { flushSync } from "react-dom";
8+
import { ErrorBoundary } from "react-error-boundary";
9+
10+
import type { DataState, ErrorLike, OperationVariables } from "@apollo/client";
11+
import {
12+
type QueryRef,
13+
type useLoadableQuery,
14+
useReadQuery,
15+
} from "@apollo/client/react";
16+
import { invariant } from "@apollo/client/utilities/invariant";
17+
18+
export async function renderUseLoadableQueryHook<
19+
TData,
20+
TVariables extends OperationVariables,
21+
TStates extends DataState<TData>["dataState"] = DataState<TData>["dataState"],
22+
Props = never,
23+
>(
24+
renderHook: (
25+
props: Props extends never ? undefined : Props
26+
) => useLoadableQuery.Result<TData, TVariables, TStates>,
27+
options: Pick<RenderOptions, "wrapper"> & { initialProps?: Props }
28+
) {
29+
function UseReadQuery({
30+
queryRef,
31+
}: {
32+
queryRef: QueryRef<TData, TVariables, TStates>;
33+
}) {
34+
useTrackRenders({ name: "useReadQuery" });
35+
mergeSnapshot({ result: useReadQuery(queryRef) });
36+
37+
return null;
38+
}
39+
40+
function SuspenseFallback() {
41+
useTrackRenders({ name: "<Suspense />" });
42+
43+
return null;
44+
}
45+
46+
function ErrorFallback() {
47+
useTrackRenders({ name: "<ErrorBoundary />" });
48+
49+
return null;
50+
}
51+
52+
type RefetchFunction = useLoadableQuery.Handlers<
53+
TData,
54+
TVariables
55+
>["refetch"];
56+
57+
function App({ props }: { props: Props | undefined }) {
58+
useTrackRenders({ name: "useLoadableQuery" });
59+
const [loadQuery, queryRef, { refetch }] = renderHook(props as any);
60+
61+
mergeSnapshot({ loadQuery, refetch });
62+
63+
return (
64+
<Suspense fallback={<SuspenseFallback />}>
65+
<ErrorBoundary
66+
FallbackComponent={ErrorFallback}
67+
onError={(error) => replaceSnapshot({ error })}
68+
>
69+
{queryRef && <UseReadQuery queryRef={queryRef} />}
70+
</ErrorBoundary>
71+
</Suspense>
72+
);
73+
}
74+
75+
const {
76+
render,
77+
getCurrentRender,
78+
takeRender,
79+
mergeSnapshot,
80+
replaceSnapshot,
81+
} = createRenderStream<
82+
| {
83+
loadQuery: useLoadableQuery.LoadQueryFunction<TVariables>;
84+
refetch: RefetchFunction;
85+
result?: useReadQuery.Result<TData, TStates>;
86+
}
87+
| { error: ErrorLike }
88+
>({
89+
// These values should always be available, but createRenderStream needs an
90+
// initial snapshot when using mergeSnapshot so we provide it with something
91+
initialSnapshot: { loadQuery: null as any, refetch: null as any },
92+
});
93+
94+
const utils = await render(<App props={options.initialProps} />, options);
95+
96+
function rerender(props: Props) {
97+
return utils.rerender(<App props={props} />);
98+
}
99+
100+
function getCurrentSnapshot() {
101+
const { snapshot } = getCurrentRender();
102+
invariant(
103+
"loadQuery" in snapshot,
104+
"Expected rendered hook instead of error boundary"
105+
);
106+
107+
return snapshot;
108+
}
109+
110+
// React 18 skips committing the suspense fallback when loadQuery/refetch is
111+
// triggered as a default-priority update, so the fallback render is missed
112+
// and tests may fail. flushSync forces a synchronous commit so the fallback
113+
// renders in both React 18 and 19, while returning the underlying result.
114+
const loadQuery: useLoadableQuery.LoadQueryFunction<TVariables> = (
115+
...args
116+
) => {
117+
return flushSync(() => getCurrentSnapshot().loadQuery(...args));
118+
};
119+
120+
const refetch: RefetchFunction = (...args) => {
121+
return flushSync(() => getCurrentSnapshot().refetch(...args));
122+
};
123+
124+
return { takeRender, rerender, getCurrentSnapshot, refetch, loadQuery };
125+
}

0 commit comments

Comments
 (0)