diff --git a/.eslintrc b/.eslintrc index 74ac0df1a68..93b6da77d7d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -90,7 +90,8 @@ "testing-library/prefer-user-event": "error", "testing-library/no-wait-for-multiple-assertions": "off", "local-rules/require-using-disposable": "error", - "local-rules/require-disable-act-environment": "error" + "local-rules/require-disable-act-environment": "error", + "local-rules/forbid-act-in-disabled-act-environment": "error" } } ], diff --git a/eslint-local-rules/forbid-act-in-disabled-act-environment.test.ts b/eslint-local-rules/forbid-act-in-disabled-act-environment.test.ts new file mode 100644 index 00000000000..8024e91bcc5 --- /dev/null +++ b/eslint-local-rules/forbid-act-in-disabled-act-environment.test.ts @@ -0,0 +1,56 @@ +import { rule } from "./forbid-act-in-disabled-act-environment"; +import { ruleTester } from "./testSetup"; + +ruleTester.run("forbid-act-in-disabled-act-environment", rule, { + valid: [ + ` + () => { + using _disabledAct = disableActEnvironment(); + } + () => { + act(() => {}) + } + `, + ` + () => { + using _disabledAct = disableActEnvironment(); + } + () => { + actAsync(() => {}) + } + `, + ], + invalid: [ + ` + () => { + using _disabledAct = disableActEnvironment(); + act(() => {}) + } + `, + ` + () => { + using _disabledAct = disableActEnvironment(); + actAsync(() => {}) + } + `, + ` + () => { + using _disabledAct = disableActEnvironment(); + () => { + act(() => {}) + } + } + `, + ` + () => { + using _disabledAct = disableActEnvironment(); + () => { + actAsync(() => {}) + } + } + `, + ].map((code) => ({ + code, + errors: [{ messageId: "forbiddenActInNonActEnvironment" }], + })), +}); diff --git a/eslint-local-rules/forbid-act-in-disabled-act-environment.ts b/eslint-local-rules/forbid-act-in-disabled-act-environment.ts new file mode 100644 index 00000000000..7cbe9edf572 --- /dev/null +++ b/eslint-local-rules/forbid-act-in-disabled-act-environment.ts @@ -0,0 +1,63 @@ +import { ESLintUtils } from "@typescript-eslint/utils"; + +export const rule = ESLintUtils.RuleCreator.withoutDocs({ + create(context) { + let depth = 1; + let disabledDepth: number | false = false; + + function EnterFn() { + depth++; + } + function ExitFn() { + depth--; + if (disabledDepth !== false && disabledDepth > depth) { + disabledDepth = false; + } + } + + return { + CallExpression(node) { + const directCallee = + node.callee.type === "Identifier" ? node.callee + : node.callee.type === "MemberExpression" ? node.callee.property + : null; + + if ( + directCallee?.type === "Identifier" && + directCallee.name === "disableActEnvironment" + ) { + if (disabledDepth === false) { + disabledDepth = depth; + } + } + + if ( + directCallee?.type === "Identifier" && + (directCallee.name === "act" || directCallee.name === "actAsync") + ) { + if (disabledDepth !== false) { + context.report({ + messageId: "forbiddenActInNonActEnvironment", + node: node, + }); + } + } + }, + ArrowFunctionExpression: EnterFn, + FunctionExpression: EnterFn, + FunctionDeclaration: EnterFn, + "ArrowFunctionExpression:exit": ExitFn, + "FunctionExpression:exit": ExitFn, + "FunctionDeclaration:exit": ExitFn, + }; + }, + meta: { + messages: { + forbiddenActInNonActEnvironment: + "`act` should not be called in a `disableActEnvironment`.", + }, + type: "problem", + schema: [], + }, + defaultOptions: [], +}); diff --git a/eslint-local-rules/index.js b/eslint-local-rules/index.js index b54f8940986..34951395d60 100644 --- a/eslint-local-rules/index.js +++ b/eslint-local-rules/index.js @@ -13,4 +13,6 @@ module.exports = { "require-using-disposable": require("./require-using-disposable").rule, "require-disable-act-environment": require("./require-disable-act-environment").rule, + "forbid-act-in-disabled-act-environment": + require("./forbid-act-in-disabled-act-environment").rule, }; diff --git a/src/react/hooks/__tests__/useFragment.test.tsx b/src/react/hooks/__tests__/useFragment.test.tsx index 3dbce16c20b..5d362c5985e 100644 --- a/src/react/hooks/__tests__/useFragment.test.tsx +++ b/src/react/hooks/__tests__/useFragment.test.tsx @@ -2002,12 +2002,9 @@ describe("has the same timing as `useQuery`", () => { expect(withinDOM().queryAllByText(/Item #2/).length).toBe(2); } - act( - () => - void cache.evict({ - id: cache.identify(item2), - }) - ); + cache.evict({ + id: cache.identify(item2), + }); { const { withinDOM } = await takeRender(); diff --git a/src/react/hooks/__tests__/useLoadableQuery.test.tsx b/src/react/hooks/__tests__/useLoadableQuery.test.tsx index 80a2dfef744..c6eace842a9 100644 --- a/src/react/hooks/__tests__/useLoadableQuery.test.tsx +++ b/src/react/hooks/__tests__/useLoadableQuery.test.tsx @@ -64,6 +64,7 @@ import { createRenderStream, disableActEnvironment, useTrackRenders, + userEventWithoutAct, } from "@testing-library/react-render-stream"; const IS_REACT_19 = React.version.startsWith("19"); @@ -230,11 +231,9 @@ function createDefaultProfiledComponents< async function renderWithMocks( ui: React.ReactElement, props: MockedProviderProps, - { render: doRender }: { render: RenderWithoutActAsync } = { - render: renderAsync, - } + { render: doRender }: { render: RenderWithoutActAsync } ) { - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const utils = await doRender(ui, { wrapper: ({ children }) => ( @@ -248,12 +247,10 @@ async function renderWithMocks( async function renderWithClient( ui: React.ReactElement, options: { client: ApolloClient }, - { render: doRender }: { render: RenderWithoutActAsync } = { - render: renderAsync, - } + { render: doRender }: { render: RenderWithoutActAsync } ) { const { client } = options; - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const utils = await doRender(ui, { wrapper: ({ children }: { children: React.ReactNode }) => ( @@ -301,7 +298,7 @@ it("loads a query and suspends when the load query function is called", async () expect(renderedComponents).toStrictEqual([App]); } - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -359,7 +356,7 @@ it("loads a query with variables and suspends by passing variables to the loadQu expect(renderedComponents).toStrictEqual([App]); } - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -419,7 +416,7 @@ it("tears down the query on unmount", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); const { snapshot } = await renderStream.takeRender(); @@ -556,7 +553,7 @@ it("will resubscribe after disposed when mounting useReadQuery", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); expect(client.getObservableQueries().size).toBe(1); expect(client).toHaveSuspenseCacheEntryUsing(query); @@ -573,7 +570,7 @@ it("will resubscribe after disposed when mounting useReadQuery", async () => { expect(client.getObservableQueries().size).toBe(0); expect(client).not.toHaveSuspenseCacheEntryUsing(query); - await act(() => user.click(screen.getByText("Toggle"))); + await user.click(screen.getByText("Toggle")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -644,7 +641,7 @@ it("auto resubscribes when mounting useReadQuery after naturally disposed by use // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); expect(client.getObservableQueries().size).toBe(1); expect(client).toHaveSuspenseCacheEntryUsing(query); @@ -665,14 +662,14 @@ it("auto resubscribes when mounting useReadQuery after naturally disposed by use }); } - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); expect(client.getObservableQueries().size).toBe(0); expect(client).not.toHaveSuspenseCacheEntryUsing(query); - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(client.getObservableQueries().size).toBe(1); // Here we don't expect a suspense cache entry because we previously disposed @@ -752,7 +749,7 @@ it("changes variables on a query and resuspends when passing new variables to th expect(renderedComponents).toStrictEqual([App]); } - await act(() => user.click(screen.getByText("Load 1st character"))); + await user.click(screen.getByText("Load 1st character")); { const { renderedComponents } = await renderStream.takeRender(); @@ -771,7 +768,7 @@ it("changes variables on a query and resuspends when passing new variables to th }); } - await act(() => user.click(screen.getByText("Load 2nd character"))); + await user.click(screen.getByText("Load 2nd character")); { const { renderedComponents } = await renderStream.takeRender(); @@ -836,7 +833,7 @@ it("resets the `queryRef` to null and disposes of it when calling the `reset` fu // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -849,7 +846,7 @@ it("resets the `queryRef` to null and disposes of it when calling the `reset` fu }); } - await act(() => user.click(screen.getByText("Reset query"))); + await user.click(screen.getByText("Reset query")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -923,7 +920,7 @@ it("allows the client to be overridden", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); const { snapshot } = await renderStream.takeRender(); @@ -992,7 +989,7 @@ it("passes context to the link", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); const { snapshot } = await renderStream.takeRender(); @@ -1082,7 +1079,7 @@ it('enables canonical results when canonizeResults is "true"', async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); const { snapshot } = await renderStream.takeRender(); const resultSet = new Set(snapshot.result?.data.results); @@ -1171,7 +1168,7 @@ it("can disable canonical results when the cache's canonizeResults setting is tr // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); const { snapshot } = await renderStream.takeRender(); const resultSet = new Set(snapshot.result!.data.results); @@ -1239,7 +1236,7 @@ it("returns initial cache data followed by network data when the fetch policy is // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -1316,7 +1313,7 @@ it("all data is present in the cache, no network request is made", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -1388,7 +1385,7 @@ it("partial data is present in the cache so it is ignored and network request is // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1461,7 +1458,7 @@ it("existing data in the cache is ignored when `fetchPolicy` is 'network-only'", // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1531,7 +1528,7 @@ it("fetches data from the network but does not update the cache when `fetchPolic // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1650,7 +1647,8 @@ it("works with startTransition to change variables", async () => { ); } - const { user } = await renderWithClient(, { client }); + await renderWithClient(, { client }, { render: renderAsync }); + const user = userEvent.setup(); await act(() => user.click(screen.getByText("Load first todo"))); @@ -1753,7 +1751,7 @@ it('does not suspend deferred queries with data in the cache and using a "cache- // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load todo"))); + await user.click(screen.getByText("Load todo")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -1879,7 +1877,7 @@ it("reacts to cache updates", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -1969,7 +1967,7 @@ it("applies `errorPolicy` on next fetch when it changes between renders", async // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -1982,10 +1980,10 @@ it("applies `errorPolicy` on next fetch when it changes between renders", async }); } - await act(() => user.click(screen.getByText("Change error policy"))); + await user.click(screen.getByText("Change error policy")); await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Refetch greeting"))); + await user.click(screen.getByText("Refetch greeting")); await renderStream.takeRender(); { @@ -2066,7 +2064,7 @@ it("applies `context` on next fetch when it changes between renders", async () = // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -2077,10 +2075,10 @@ it("applies `context` on next fetch when it changes between renders", async () = }); } - await act(() => user.click(screen.getByText("Update context"))); + await user.click(screen.getByText("Update context")); await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -2176,7 +2174,7 @@ it("returns canonical results immediately when `canonizeResults` changes from `f // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot } = await renderStream.takeRender(); @@ -2189,7 +2187,7 @@ it("returns canonical results immediately when `canonizeResults` changes from `f expect(values).toEqual([0, 1, 1, 2, 3, 5]); } - await act(() => user.click(screen.getByText("Canonize results"))); + await user.click(screen.getByText("Canonize results")); { const { snapshot } = await renderStream.takeRender(); @@ -2301,7 +2299,7 @@ it("applies changed `refetchWritePolicy` to next fetch when changing between ren // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -2312,7 +2310,7 @@ it("applies changed `refetchWritePolicy` to next fetch when changing between ren expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); } - await act(() => user.click(screen.getByText("Refetch next"))); + await user.click(screen.getByText("Refetch next")); await renderStream.takeRender(); { @@ -2329,10 +2327,10 @@ it("applies changed `refetchWritePolicy` to next fetch when changing between ren ]); } - await act(() => user.click(screen.getByText("Change refetch write policy"))); + await user.click(screen.getByText("Change refetch write policy")); await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Refetch last"))); + await user.click(screen.getByText("Refetch last")); await renderStream.takeRender(); { @@ -2464,7 +2462,7 @@ it("applies `returnPartialData` on next fetch when it changes between renders", // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -2479,7 +2477,7 @@ it("applies `returnPartialData` on next fetch when it changes between renders", }); } - await act(() => user.click(screen.getByText("Update partial data"))); + await user.click(screen.getByText("Update partial data")); await renderStream.takeRender(); cache.modify({ @@ -2612,7 +2610,7 @@ it("applies updated `fetchPolicy` on next fetch when it changes between renders" // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot } = await renderStream.takeRender(); @@ -2630,10 +2628,10 @@ it("applies updated `fetchPolicy` on next fetch when it changes between renders" }); } - await act(() => user.click(screen.getByText("Change fetch policy"))); + await user.click(screen.getByText("Change fetch policy")); await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -2715,7 +2713,7 @@ it("re-suspends when calling `refetch`", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -2733,7 +2731,7 @@ it("re-suspends when calling `refetch`", async () => { }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); { const { renderedComponents } = await renderStream.takeRender(); @@ -2805,7 +2803,7 @@ it("re-suspends when calling `refetch` with new variables", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -2823,7 +2821,7 @@ it("re-suspends when calling `refetch` with new variables", async () => { }); } - await act(() => user.click(screen.getByText("Refetch with ID 2"))); + await user.click(screen.getByText("Refetch with ID 2")); { const { renderedComponents } = await renderStream.takeRender(); @@ -2890,7 +2888,7 @@ it("re-suspends multiple times when calling `refetch` multiple times", async () // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -2910,7 +2908,7 @@ it("re-suspends multiple times when calling `refetch` multiple times", async () const button = screen.getByText("Refetch"); - await act(() => user.click(button)); + await user.click(button); { const { renderedComponents } = await renderStream.takeRender(); @@ -2928,7 +2926,7 @@ it("re-suspends multiple times when calling `refetch` multiple times", async () }); } - await act(() => user.click(button)); + await user.click(button); { const { renderedComponents } = await renderStream.takeRender(); @@ -3005,7 +3003,7 @@ it("throws errors when errors are returned after calling `refetch`", async () => // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3018,7 +3016,7 @@ it("throws errors when errors are returned after calling `refetch`", async () => }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -3089,7 +3087,7 @@ it('ignores errors returned after calling `refetch` when errorPolicy is set to " // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3102,7 +3100,7 @@ it('ignores errors returned after calling `refetch` when errorPolicy is set to " }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -3177,7 +3175,7 @@ it('returns errors after calling `refetch` when errorPolicy is set to "all"', as // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3190,7 +3188,7 @@ it('returns errors after calling `refetch` when errorPolicy is set to "all"', as }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -3267,7 +3265,7 @@ it('handles partial data results after calling `refetch` when errorPolicy is set // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3280,7 +3278,7 @@ it('handles partial data results after calling `refetch` when errorPolicy is set }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -3391,7 +3389,8 @@ it("`refetch` works with startTransition to allow React to show stale UI until f ); } - const { user } = await renderWithMocks(, { mocks }); + await renderWithMocks(, { mocks }, { render: renderAsync }); + const user = userEvent.setup(); await act(() => user.click(screen.getByText("Load query"))); @@ -3480,7 +3479,7 @@ it("re-suspends when calling `fetchMore` with different variables", async () => // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3498,7 +3497,7 @@ it("re-suspends when calling `fetchMore` with different variables", async () => }); } - await act(() => user.click(screen.getByText("Fetch more"))); + await user.click(screen.getByText("Fetch more")); { const { renderedComponents } = await renderStream.takeRender(); @@ -3569,7 +3568,7 @@ it("properly uses `updateQuery` when calling `fetchMore`", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3587,7 +3586,7 @@ it("properly uses `updateQuery` when calling `fetchMore`", async () => { }); } - await act(() => user.click(screen.getByText("Fetch more"))); + await user.click(screen.getByText("Fetch more")); await renderStream.takeRender(); { @@ -3663,7 +3662,7 @@ it("properly uses cache field policies when calling `fetchMore` without `updateQ // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3681,7 +3680,7 @@ it("properly uses cache field policies when calling `fetchMore` without `updateQ }); } - await act(() => user.click(screen.getByText("Fetch more"))); + await user.click(screen.getByText("Fetch more")); await renderStream.takeRender(); { @@ -3831,7 +3830,8 @@ it("`fetchMore` works with startTransition to allow React to show stale UI until ); } - const { user } = await renderWithClient(, { client }); + await renderWithClient(, { client }, { render: renderAsync }); + const user = userEvent.setup(); await act(() => user.click(screen.getByText("Load query"))); @@ -3956,7 +3956,7 @@ it('honors refetchWritePolicy set to "merge"', async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -3970,7 +3970,7 @@ it('honors refetchWritePolicy set to "merge"', async () => { expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -4075,7 +4075,7 @@ it('defaults refetchWritePolicy to "overwrite"', async () => { // initial load await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); await renderStream.takeRender(); { @@ -4089,7 +4089,7 @@ it('defaults refetchWritePolicy to "overwrite"', async () => { expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -4181,7 +4181,7 @@ it('does not suspend when partial data is in the cache and using a "cache-first" // initial load await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -4264,7 +4264,7 @@ it('suspends and does not use partial data from other variables in the cache whe // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -4290,7 +4290,7 @@ it('suspends and does not use partial data from other variables in the cache whe expect(renderedComponents).toStrictEqual([ReadQueryHook]); } - await act(() => user.click(screen.getByText("Change variables"))); + await user.click(screen.getByText("Change variables")); { const { renderedComponents } = await renderStream.takeRender(); @@ -4386,7 +4386,7 @@ it('suspends when partial data is in the cache and using a "network-only" fetch // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -4482,7 +4482,7 @@ it('suspends when partial data is in the cache and using a "no-cache" fetch poli // initial load await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -4602,7 +4602,7 @@ it('does not suspend when partial data is in the cache and using a "cache-and-ne // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -4680,7 +4680,7 @@ it('suspends and does not use partial data when changing variables and using a " // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -4704,7 +4704,7 @@ it('suspends and does not use partial data when changing variables and using a " }); } - await act(() => user.click(screen.getByText("Change variables"))); + await user.click(screen.getByText("Change variables")); { const { renderedComponents } = await renderStream.takeRender(); @@ -4802,7 +4802,7 @@ it('does not suspend deferred queries with partial data in the cache and using a // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load todo"))); + await user.click(screen.getByText("Load todo")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -4898,7 +4898,15 @@ it("throws when calling loadQuery on first render", async () => { return null; } - await expect(() => renderWithMocks(, { mocks })).rejects.toThrow( + await expect( + renderWithMocks( + , + { mocks }, + { + render: renderAsync, + } + ) + ).rejects.toThrow( new InvariantError( "useLoadableQuery: 'loadQuery' should not be called during render. To start a query during render, use the 'useBackgroundQuery' hook." ) @@ -4924,12 +4932,14 @@ it("throws when calling loadQuery on subsequent render", async () => { return ; } - const { user } = await renderWithMocks( + await renderWithMocks( (error = e)} fallback={
Oops
}>
, - { mocks } + { mocks }, + { render: renderAsync } ); + const user = userEvent.setup(); await act(() => user.click(screen.getByText("Load query in render"))); @@ -4953,8 +4963,8 @@ it("allows loadQuery to be called in useEffect on first render", async () => { return null; } - await expect(() => - renderWithMocks(, { mocks }) + await expect( + renderWithMocks(, { mocks }, { render: renderAsync }) ).resolves.not.toThrow(); }); @@ -5040,7 +5050,7 @@ it("can subscribe to subscriptions and react to cache updates via `subscribeToMo // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); diff --git a/src/react/hooks/__tests__/useMutation.test.tsx b/src/react/hooks/__tests__/useMutation.test.tsx index 4ef71758a8d..3379b5a1aaf 100644 --- a/src/react/hooks/__tests__/useMutation.test.tsx +++ b/src/react/hooks/__tests__/useMutation.test.tsx @@ -781,11 +781,8 @@ describe("useMutation Hook", () => { expect(result.called).toBe(false); } - let fetchResult: any; - act(() => { - fetchResult = createTodo({ - variables: { priority: "Low", description: "Get milk." }, - }); + let fetchResult = createTodo({ + variables: { priority: "Low", description: "Get milk." }, }); { @@ -796,7 +793,7 @@ describe("useMutation Hook", () => { expect(result.called).toBe(true); } - act(() => reset()); + reset(); { const [, result] = await takeSnapshot(); diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index daf4fbdad45..6cbd077b2ca 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -7,7 +7,6 @@ import { render, screen, waitFor, renderHook } from "@testing-library/react"; import { ApolloClient, ApolloError, - ApolloQueryResult, NetworkStatus, OperationVariables, TypedDocumentNode, @@ -32,7 +31,6 @@ import { useQuery } from "../useQuery"; import { useMutation } from "../useMutation"; import { disableActWarnings, - PaginatedCaseData, setupPaginatedCase, spyOnConsole, } from "../../../testing/internal"; @@ -44,6 +42,7 @@ import { createRenderStream, renderHookToSnapshotStream, disableActEnvironment, + userEventWithoutAct, } from "@testing-library/react-render-stream"; const IS_REACT_17 = React.version.startsWith("17"); @@ -4116,12 +4115,10 @@ describe("useQuery Hook", () => { const { fetchMore } = getCurrentSnapshot(); - await act(() => - fetchMore({ - variables: { offset: 2 }, - updateQuery: (_, { fetchMoreResult }) => fetchMoreResult, - }) - ); + await fetchMore({ + variables: { offset: 2 }, + updateQuery: (_, { fetchMoreResult }) => fetchMoreResult, + }); expect(fetches).toStrictEqual([ { variables: { limit: 2 } }, @@ -4171,16 +4168,13 @@ describe("useQuery Hook", () => { }); } - let fetchMorePromise!: Promise>; const { fetchMore } = getCurrentSnapshot(); - act(() => { - fetchMorePromise = fetchMore({ - variables: { offset: 2 }, - updateQuery: (prev, { fetchMoreResult }) => ({ - letters: prev.letters.concat(fetchMoreResult.letters), - }), - }); + let fetchMorePromise = fetchMore({ + variables: { offset: 2 }, + updateQuery: (prev, { fetchMoreResult }) => ({ + letters: prev.letters.concat(fetchMoreResult.letters), + }), }); { @@ -4235,11 +4229,9 @@ describe("useQuery Hook", () => { await expect(takeSnapshot).not.toRerender(); - act(() => { - fetchMorePromise = fetchMore({ - variables: { offset: 4 }, - updateQuery: (_, { fetchMoreResult }) => fetchMoreResult, - }); + fetchMorePromise = fetchMore({ + variables: { offset: 4 }, + updateQuery: (_, { fetchMoreResult }) => fetchMoreResult, }); { @@ -4365,12 +4357,10 @@ describe("useQuery Hook", () => { await takeSnapshot(); const { fetchMore } = getCurrentSnapshot(); - await act(() => - fetchMore({ - variables: { offset: 2 }, - updateQuery: (_, { fetchMoreResult }) => fetchMoreResult, - }) - ); + await fetchMore({ + variables: { offset: 2 }, + updateQuery: (_, { fetchMoreResult }) => fetchMoreResult, + }); expect(client.extract()).toStrictEqual({}); }); @@ -4493,7 +4483,7 @@ describe("useQuery Hook", () => { id: number; } - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const query1: TypedDocumentNode = gql` query PersonQuery1($id: ID!) { @@ -4617,7 +4607,7 @@ describe("useQuery Hook", () => { }); } - await act(() => user.click(screen.getByText("Run 2nd query"))); + await user.click(screen.getByText("Run 2nd query")); { const { snapshot } = await renderStream.takeRender(); @@ -4674,7 +4664,7 @@ describe("useQuery Hook", () => { }); } - await act(() => user.click(screen.getByText("Reload 1st query"))); + await user.click(screen.getByText("Reload 1st query")); { const { snapshot } = await renderStream.takeRender(); @@ -4753,7 +4743,7 @@ describe("useQuery Hook", () => { id: number; } - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const query1: TypedDocumentNode = gql` query PersonQuery1($id: ID!) { @@ -4869,7 +4859,7 @@ describe("useQuery Hook", () => { }); } - await act(() => user.click(screen.getByText("Run 2nd query"))); + await user.click(screen.getByText("Run 2nd query")); { const { snapshot } = await renderStream.takeRender(); @@ -4976,7 +4966,7 @@ describe("useQuery Hook", () => { id: number; } - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const query1: TypedDocumentNode = gql` query PersonQuery1($id: ID!) { @@ -5092,7 +5082,7 @@ describe("useQuery Hook", () => { }); } - await act(() => user.click(screen.getByText("Run 2nd query"))); + await user.click(screen.getByText("Run 2nd query")); { const { snapshot } = await renderStream.takeRender(); @@ -5275,7 +5265,7 @@ describe("useQuery Hook", () => { } `; - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); using _disabledAct = disableActEnvironment(); const renderStream = createRenderStream({ @@ -5404,7 +5394,7 @@ describe("useQuery Hook", () => { }); } - await act(() => user.click(screen.getByText("Run mutation"))); + await user.click(screen.getByText("Run mutation")); await renderStream.takeRender(); { @@ -6702,7 +6692,7 @@ describe("useQuery Hook", () => { expect(query.data).toEqual(carsData); } - act(() => void mutate()); + mutate(); { // The mutation ran and is loading the result. The query stays at not diff --git a/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx b/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx index fd2f3fc939b..3c678892c2b 100644 --- a/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx +++ b/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { act, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import { ApolloClient, InMemoryCache, @@ -34,6 +34,7 @@ import { concatPagination, getMainDefinition } from "../../../utilities"; import { createRenderStream, disableActEnvironment, + userEventWithoutAct, useTrackRenders, } from "@testing-library/react-render-stream"; @@ -130,7 +131,7 @@ test("does not interfere with updates from useReadQuery", async () => { test("refetches and resuspends when calling refetch", async () => { const { query, mocks: defaultMocks } = setupSimpleCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const mocks = [ defaultMocks[0], @@ -199,7 +200,7 @@ test("refetches and resuspends when calling refetch", async () => { }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); { const { renderedComponents } = await renderStream.takeRender(); @@ -262,7 +263,7 @@ test('honors refetchWritePolicy set to "merge"', async () => { }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ link: new MockLink(mocks), cache, @@ -322,7 +323,7 @@ test('honors refetchWritePolicy set to "merge"', async () => { expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -389,7 +390,7 @@ test('honors refetchWritePolicy set to "overwrite"', async () => { }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ link: new MockLink(mocks), cache, @@ -449,7 +450,7 @@ test('honors refetchWritePolicy set to "overwrite"', async () => { expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -513,7 +514,7 @@ test('defaults refetchWritePolicy to "overwrite"', async () => { }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ link: new MockLink(mocks), cache, @@ -572,7 +573,7 @@ test('defaults refetchWritePolicy to "overwrite"', async () => { expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); await renderStream.takeRender(); { @@ -604,7 +605,7 @@ test("`refetch` works with startTransition", async () => { completed: boolean; }; } - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const query: TypedDocumentNode = gql` query TodoItemQuery($id: ID!) { @@ -717,7 +718,7 @@ test("`refetch` works with startTransition", async () => { } const button = screen.getByText("Refetch"); - await act(() => user.click(button)); + await user.click(button); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -753,7 +754,7 @@ test("`refetch` works with startTransition", async () => { test("`refetch` works with startTransition from useBackgroundQuery and usePreloadedQueryHandlers", async () => { const { query, mocks: defaultMocks } = setupSimpleCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const mocks = [ defaultMocks[0], @@ -854,7 +855,7 @@ test("`refetch` works with startTransition from useBackgroundQuery and usePreloa }); } - await act(() => user.click(screen.getByText("Refetch from parent"))); + await user.click(screen.getByText("Refetch from parent")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -886,7 +887,7 @@ test("`refetch` works with startTransition from useBackgroundQuery and usePreloa }); } - await act(() => user.click(screen.getByText("Refetch from child"))); + await user.click(screen.getByText("Refetch from child")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -924,7 +925,7 @@ test("`refetch` works with startTransition from useBackgroundQuery and usePreloa test("refetches from queryRefs produced by useBackgroundQuery", async () => { const { query, mocks: defaultMocks } = setupSimpleCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const mocks = [ defaultMocks[0], @@ -990,7 +991,7 @@ test("refetches from queryRefs produced by useBackgroundQuery", async () => { }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1012,7 +1013,7 @@ test("refetches from queryRefs produced by useBackgroundQuery", async () => { test("refetches from queryRefs produced by useLoadableQuery", async () => { const { query, mocks: defaultMocks } = setupSimpleCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const mocks = [ defaultMocks[0], @@ -1066,7 +1067,7 @@ test("refetches from queryRefs produced by useLoadableQuery", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1084,7 +1085,7 @@ test("refetches from queryRefs produced by useLoadableQuery", async () => { }); } - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1106,7 +1107,7 @@ test("refetches from queryRefs produced by useLoadableQuery", async () => { test("resuspends when calling `fetchMore`", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ @@ -1183,7 +1184,7 @@ test("resuspends when calling `fetchMore`", async () => { }); } - await act(() => user.click(screen.getByText("Load next"))); + await user.click(screen.getByText("Load next")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1210,7 +1211,7 @@ test("resuspends when calling `fetchMore`", async () => { test("properly uses `updateQuery` when calling `fetchMore`", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache(), link }); const preloadQuery = createQueryPreloader(client); @@ -1283,7 +1284,7 @@ test("properly uses `updateQuery` when calling `fetchMore`", async () => { }); } - await act(() => user.click(screen.getByText("Load next"))); + await user.click(screen.getByText("Load next")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1312,7 +1313,7 @@ test("properly uses `updateQuery` when calling `fetchMore`", async () => { test("properly uses cache field policies when calling `fetchMore` without `updateQuery`", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ @@ -1389,7 +1390,7 @@ test("properly uses cache field policies when calling `fetchMore` without `updat }); } - await act(() => user.click(screen.getByText("Load next"))); + await user.click(screen.getByText("Load next")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1418,7 +1419,7 @@ test("properly uses cache field policies when calling `fetchMore` without `updat test("paginates from queryRefs produced by useBackgroundQuery", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { @@ -1495,7 +1496,7 @@ test("paginates from queryRefs produced by useBackgroundQuery", async () => { }); } - await act(() => user.click(screen.getByText("Load next"))); + await user.click(screen.getByText("Load next")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1522,7 +1523,7 @@ test("paginates from queryRefs produced by useBackgroundQuery", async () => { test("paginates from queryRefs produced by useLoadableQuery", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { @@ -1584,7 +1585,7 @@ test("paginates from queryRefs produced by useLoadableQuery", async () => { // initial render await renderStream.takeRender(); - await act(() => user.click(screen.getByText("Load query"))); + await user.click(screen.getByText("Load query")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1607,7 +1608,7 @@ test("paginates from queryRefs produced by useLoadableQuery", async () => { }); } - await act(() => user.click(screen.getByText("Load next"))); + await user.click(screen.getByText("Load next")); { const { renderedComponents } = await renderStream.takeRender(); @@ -1634,7 +1635,7 @@ test("paginates from queryRefs produced by useLoadableQuery", async () => { test("`fetchMore` works with startTransition", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { @@ -1719,7 +1720,7 @@ test("`fetchMore` works with startTransition", async () => { }); } - await act(() => user.click(screen.getByText("Load next"))); + await user.click(screen.getByText("Load next")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -1765,7 +1766,7 @@ test("`fetchMore` works with startTransition", async () => { test("`fetchMore` works with startTransition from useBackgroundQuery and useQueryRefHandlers", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { @@ -1868,7 +1869,7 @@ test("`fetchMore` works with startTransition from useBackgroundQuery and useQuer }); } - await act(() => user.click(screen.getByText("Paginate from parent"))); + await user.click(screen.getByText("Paginate from parent")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -1910,7 +1911,7 @@ test("`fetchMore` works with startTransition from useBackgroundQuery and useQuer }); } - await act(() => user.click(screen.getByText("Paginate from child"))); + await user.click(screen.getByText("Paginate from child")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx index f517e25de41..e7f85cb3a37 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx @@ -10172,7 +10172,7 @@ describe("useSuspenseQuery", () => { it("fetchMore does not cause extra render", async () => { const { query, link } = setupPaginatedCase(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { @@ -10260,7 +10260,7 @@ describe("useSuspenseQuery", () => { }); } - await act(() => user.click(screen.getByText("Fetch next"))); + await user.click(screen.getByText("Fetch next")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -10308,7 +10308,7 @@ describe("useSuspenseQuery", () => { interface Data { todos: Todo[]; } - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const query: TypedDocumentNode = gql` query TodosQuery($offset: Int!) { @@ -10457,7 +10457,7 @@ describe("useSuspenseQuery", () => { }); } - await act(() => user.click(screen.getByText("Load more"))); + await user.click(screen.getByText("Load more")); { const { snapshot, renderedComponents } = await renderStream.takeRender(); @@ -10550,7 +10550,7 @@ describe("useSuspenseQuery", () => { }, ]); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { @@ -10625,7 +10625,7 @@ describe("useSuspenseQuery", () => { }); } - await act(() => user.click(screen.getByText("Fetch next"))); + await user.click(screen.getByText("Fetch next")); await renderStream.takeRender(); if (IS_REACT_19) { @@ -10647,7 +10647,7 @@ describe("useSuspenseQuery", () => { }); } - await act(() => user.click(screen.getByText("Fetch next"))); + await user.click(screen.getByText("Fetch next")); await renderStream.takeRender(); if (IS_REACT_19) { @@ -10768,9 +10768,7 @@ describe("useSuspenseQuery", () => { } const { snapshot } = renderStream.getCurrentRender(); - await act(() => - snapshot.result!.fetchMore({ variables: { offset: 2 } }).catch(() => {}) - ); + snapshot.result!.fetchMore({ variables: { offset: 2 } }).catch(() => {}); { const { renderedComponents } = await renderStream.takeRender(); diff --git a/src/react/query-preloader/__tests__/createQueryPreloader.test.tsx b/src/react/query-preloader/__tests__/createQueryPreloader.test.tsx index 1d8b2ec61e0..7b2a66c114c 100644 --- a/src/react/query-preloader/__tests__/createQueryPreloader.test.tsx +++ b/src/react/query-preloader/__tests__/createQueryPreloader.test.tsx @@ -28,7 +28,7 @@ import { VariablesCaseData, } from "../../../testing/internal"; import { ApolloProvider } from "../../context"; -import { act, renderHook, screen } from "@testing-library/react"; +import { act, renderHookAsync, screen } from "@testing-library/react"; import { UseReadQueryResult, useReadQuery } from "../../hooks"; import { GraphQLError } from "graphql"; import { ErrorBoundary } from "react-error-boundary"; @@ -36,6 +36,7 @@ import userEvent from "@testing-library/user-event"; import { createRenderStream, disableActEnvironment, + userEventWithoutAct, useTrackRenders, } from "@testing-library/react-render-stream"; @@ -224,7 +225,7 @@ test("useReadQuery auto-retains the queryRef and disposes of it when unmounted", const queryRef = preloadQuery(query); - const { unmount } = renderHook(() => useReadQuery(queryRef)); + const { unmount } = await renderHookAsync(() => useReadQuery(queryRef)); // We don't start the dispose timer until the promise is initially resolved // so we need to wait for it @@ -265,7 +266,7 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => result: null as UseReadQueryResult | null, }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache(), link }); const preloadQuery = createQueryPreloader(client); @@ -297,7 +298,7 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => return null; } - renderStream.render(, { wrapper: createClientWrapper(client) }); + await renderStream.render(, { wrapper: createClientWrapper(client) }); const toggleButton = screen.getByText("Toggle"); @@ -317,14 +318,14 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await wait(0); await renderStream.takeRender(); expect(queryRef).toBeDisposed(); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // Ensure we aren't refetching the data by checking we still render the same // cache result @@ -356,7 +357,7 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => } // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -366,7 +367,7 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => // instead of the old one client.writeQuery({ query, data: { greeting: "While you were away" } }); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(queryRef).not.toBeDisposed(); @@ -385,7 +386,7 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -404,7 +405,7 @@ test("useReadQuery auto-resubscribes the query after its disposed", async () => expect(fetchCount).toBe(1); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // this should now trigger a network request expect(fetchCount).toBe(2); @@ -459,7 +460,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => result: null as UseReadQueryResult> | null, }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache(), link }); const preloadQuery = createQueryPreloader(client); @@ -494,7 +495,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => return null; } - renderStream.render(, { wrapper: createClientWrapper(client) }); + await renderStream.render(, { wrapper: createClientWrapper(client) }); const toggleButton = screen.getByText("Toggle"); @@ -516,14 +517,14 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await wait(0); await renderStream.takeRender(); expect(queryRef).toBeDisposed(); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // Ensure we aren't refetching the data by checking we still render the same // cache result @@ -573,7 +574,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => } // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -593,7 +594,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => variables: { id: "1" }, }); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(queryRef).not.toBeDisposed(); @@ -618,7 +619,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -638,7 +639,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => expect(fetchCount).toBe(1); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // this should now trigger a network request expect(fetchCount).toBe(2); @@ -668,7 +669,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => } // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -676,7 +677,7 @@ test("useReadQuery handles auto-resubscribe with returnPartialData", async () => client.clearStore(); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(fetchCount).toBe(3); @@ -721,7 +722,7 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async result: null as UseReadQueryResult | null, }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache(), link }); const preloadQuery = createQueryPreloader(client); @@ -753,7 +754,7 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async return null; } - renderStream.render(, { wrapper: createClientWrapper(client) }); + await renderStream.render(, { wrapper: createClientWrapper(client) }); const toggleButton = screen.getByText("Toggle"); @@ -773,14 +774,14 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await wait(0); await renderStream.takeRender(); expect(queryRef).toBeDisposed(); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // Ensure we aren't refetching the data by checking we still render the same // cache result @@ -812,7 +813,7 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async } // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -822,7 +823,7 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async // instead of the old one client.writeQuery({ query, data: { greeting: "While you were away" } }); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(queryRef).not.toBeDisposed(); @@ -841,7 +842,7 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -859,7 +860,7 @@ test("useReadQuery handles auto-resubscribe on network-only fetch policy", async expect(fetchCount).toBe(1); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(fetchCount).toBe(2); expect(queryRef).not.toBeDisposed(); @@ -903,7 +904,7 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", result: null as UseReadQueryResult | null, }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache(), link }); const preloadQuery = createQueryPreloader(client); @@ -935,7 +936,7 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", return null; } - renderStream.render(, { wrapper: createClientWrapper(client) }); + await renderStream.render(, { wrapper: createClientWrapper(client) }); const toggleButton = screen.getByText("Toggle"); @@ -955,14 +956,14 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await wait(0); await renderStream.takeRender(); expect(queryRef).toBeDisposed(); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // Ensure we aren't refetching the data by checking we still render the same // cache result @@ -994,7 +995,7 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", } // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -1004,7 +1005,7 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", // instead of the old one client.writeQuery({ query, data: { greeting: "While you were away" } }); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(queryRef).not.toBeDisposed(); @@ -1023,7 +1024,7 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -1041,7 +1042,7 @@ test("useReadQuery handles auto-resubscribe on cache-and-network fetch policy", expect(fetchCount).toBe(1); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(fetchCount).toBe(2); expect(queryRef).not.toBeDisposed(); @@ -1085,7 +1086,7 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () result: null as UseReadQueryResult | null, }, }); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); const client = new ApolloClient({ cache: new InMemoryCache(), link }); const preloadQuery = createQueryPreloader(client); @@ -1117,7 +1118,7 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () return null; } - renderStream.render(, { wrapper: createClientWrapper(client) }); + await renderStream.render(, { wrapper: createClientWrapper(client) }); const toggleButton = screen.getByText("Toggle"); @@ -1137,14 +1138,14 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await wait(0); await renderStream.takeRender(); expect(queryRef).toBeDisposed(); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); // Ensure we aren't refetching the data by checking we still render the same // result @@ -1168,7 +1169,7 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () await expect(renderStream).not.toRerender(); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -1177,7 +1178,7 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () // Write a cache result to ensure that remounting will ignore this result client.writeQuery({ query, data: { greeting: "While you were away" } }); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(queryRef).not.toBeDisposed(); @@ -1195,7 +1196,7 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () expect(fetchCount).toBe(1); // unmount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); await renderStream.takeRender(); await wait(0); @@ -1213,7 +1214,7 @@ test("useReadQuery handles auto-resubscribe on no-cache fetch policy", async () expect(fetchCount).toBe(1); // mount ReadQueryHook - await act(() => user.click(toggleButton)); + await user.click(toggleButton); expect(fetchCount).toBe(1); expect(queryRef).not.toBeDisposed(); diff --git a/src/testing/experimental/__tests__/createTestSchema.test.tsx b/src/testing/experimental/__tests__/createTestSchema.test.tsx index 30521640ce4..1f6989eab30 100644 --- a/src/testing/experimental/__tests__/createTestSchema.test.tsx +++ b/src/testing/experimental/__tests__/createTestSchema.test.tsx @@ -12,7 +12,7 @@ import { buildSchema } from "graphql"; import type { UseSuspenseQueryResult } from "../../../react/index.js"; import { useMutation, useSuspenseQuery } from "../../../react/index.js"; import userEvent from "@testing-library/user-event"; -import { act, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import { createSchemaFetch } from "../createSchemaFetch.js"; import { FallbackProps, @@ -23,6 +23,7 @@ import { RenderStream, createRenderStream, disableActEnvironment, + userEventWithoutAct, } from "@testing-library/react-render-stream"; const typeDefs = /* GraphQL */ ` @@ -603,7 +604,7 @@ describe("schema proxy", () => { ); }; - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); renderStream.render(, { wrapper: createClientWrapper(client), @@ -632,7 +633,7 @@ describe("schema proxy", () => { }); } - await act(() => user.click(screen.getByText("Change name"))); + await user.click(screen.getByText("Change name")); // initial suspended render await renderStream.takeRender(); @@ -957,7 +958,7 @@ describe("schema proxy", () => { ); }; - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); renderStream.render(, { wrapper: createClientWrapper(client), @@ -986,7 +987,7 @@ describe("schema proxy", () => { }); } - await act(() => user.click(screen.getByText("Change name"))); + await user.click(screen.getByText("Change name")); await renderStream.takeRender(); { @@ -1130,9 +1131,9 @@ describe("schema proxy", () => { resetTestSchema.reset(); - const user = userEvent.setup(); + const user = userEventWithoutAct(userEvent.setup()); - await act(() => user.click(screen.getByText("Refetch"))); + await user.click(screen.getByText("Refetch")); // initial suspended render await renderStream.takeRender();