From 76c4e837a75531f2cbb87cbf9dc25c4dab802e8c Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Tue, 29 Aug 2023 15:35:17 +0200 Subject: [PATCH 01/14] Support re-using mocks in the MockLink --- docs/source/development-testing/testing.mdx | 31 ++++++ src/testing/core/mocking/mockLink.ts | 8 +- .../react/__tests__/MockedProvider.test.tsx | 99 +++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) diff --git a/docs/source/development-testing/testing.mdx b/docs/source/development-testing/testing.mdx index 06a69d5db25..1dd979cb0f5 100644 --- a/docs/source/development-testing/testing.mdx +++ b/docs/source/development-testing/testing.mdx @@ -150,6 +150,37 @@ it("renders without error", async () => { +#### Reusing mocks + +By default, a mock is only used once. If you want to reuse a mock for multiple operations, you can set the `reuse` field to a number indicating how many times the mock should be reused: + + + +```jsx title="dog.test.js" +import { GET_DOG_QUERY } from "./dog"; + +const mocks = [ + { + request: { + query: GET_DOG_QUERY, + variables: { + name: "Buck" + } + }, + result: { + data: { + dog: { id: "1", name: "Buck", breed: "bulldog" } + } + }, + reuse: 2, // The mock can be reused twice before it's removed, default is 0 + } +]; +``` + + + +Passing Number.POSITIVE_INFINITY as reuse will cause the mock to be reused indefinitely. + ### Setting `addTypename` In the example above, we set the `addTypename` prop of `MockedProvider` to `false`. This prevents Apollo Client from automatically adding the special `__typename` field to every object it queries for (it does this by default to support data normalization in the cache). diff --git a/src/testing/core/mocking/mockLink.ts b/src/testing/core/mocking/mockLink.ts index 7740828905c..b933b9c566a 100644 --- a/src/testing/core/mocking/mockLink.ts +++ b/src/testing/core/mocking/mockLink.ts @@ -28,6 +28,7 @@ export interface MockedResponse< > { request: GraphQLRequest; result?: FetchResult | ResultFunction>; + reuse?: number; error?: Error; delay?: number; newData?: ResultFunction; @@ -123,7 +124,12 @@ ${unmatchedVars.map(d => ` ${stringifyForDisplay(d)}`).join('\n')} ); } } else { - mockedResponses.splice(responseIndex, 1); + if (!response.reuse) { + mockedResponses.splice(responseIndex, 1); + } else if (response.reuse !== Number.POSITIVE_INFINITY) { + response.reuse--; + } + const { newData } = response; if (newData) { diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index 292d6c026d8..7b8a2855d1f 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -454,6 +454,105 @@ describe('General use', () => { expect(errorThrown).toBeFalsy(); }); + it('should reuse mocks when instructed to', async () => { + let isLoaded = false; + let isError = false; + function Component({ username }: Variables) { + const { loading, error } = useQuery(query, { variables: { username }, fetchPolicy: 'network-only' }); + if (!loading) { + if (error) { + isError = true; + } + isLoaded = true; + } + return null; + } + + const mocks: ReadonlyArray = [ + { + request: { + query, + variables: { + username: 'mock_username' + } + }, + reuse: 1, + result: { data: { user } } + }, + { + request: { + query, + variables: { + username: 'mock_username2' + } + }, + reuse: Number.POSITIVE_INFINITY, + result: { data: { user } } + }, + { + request: { + query, + variables: { + username: 'mock_username3' + } + }, + result: { data: { user } } + } + ]; + + const mockLink = new MockLink(mocks, true, { showWarnings: false }); + + const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + const reset = () => { + isLoaded = false; + isError = false; + } + const waitForLoaded = async () => { + await waitFor(() => { + expect(isLoaded).toBe(true); + expect(isError).toBe(false); + }); + } + const waitForError = async () => { + await waitFor(() => { + expect(isLoaded).toBe(true); + expect(isError).toBe(true); + }); + } + + const { rerender } = render(, { wrapper: Wrapper }); + await waitForLoaded(); + reset(); + + rerender(); + await waitForLoaded(); + reset(); + + rerender(); + await waitForLoaded(); + reset(); + + rerender(); + await waitForLoaded(); + reset(); + + rerender(); + await waitForLoaded(); + reset(); + + rerender(); + await waitForError(); + reset(); + + rerender(); + await waitForError(); + reset(); + + rerender(); + await waitForLoaded(); + reset(); + }); + it('should return "Mocked response should contain" errors in response', async () => { let finished = false; function Component({ ...variables }: Variables) { From ab11bf46fd42ce4ae78525e97fe4e832caa3fc63 Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Tue, 29 Aug 2023 15:37:28 +0200 Subject: [PATCH 02/14] Add changeset --- .changeset/yellow-flies-repeat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/yellow-flies-repeat.md diff --git a/.changeset/yellow-flies-repeat.md b/.changeset/yellow-flies-repeat.md new file mode 100644 index 00000000000..b6fcff7db25 --- /dev/null +++ b/.changeset/yellow-flies-repeat.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Support re-using of mocks in the MockedProvider From 53e494fbc7a2b8a3127af3060c0a7edf4795969f Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Tue, 29 Aug 2023 15:43:23 +0200 Subject: [PATCH 03/14] Improve documentation --- docs/source/development-testing/testing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/development-testing/testing.mdx b/docs/source/development-testing/testing.mdx index 1dd979cb0f5..f9a07d9e5a3 100644 --- a/docs/source/development-testing/testing.mdx +++ b/docs/source/development-testing/testing.mdx @@ -179,7 +179,7 @@ const mocks = [ -Passing Number.POSITIVE_INFINITY as reuse will cause the mock to be reused indefinitely. +Passing `Number.POSITIVE_INFINITY` will cause the mock to be reused indefinitely. ### Setting `addTypename` From 85e272c00243278ec87f25960161f0bcff18ca17 Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Wed, 30 Aug 2023 12:15:50 +0200 Subject: [PATCH 04/14] Remove useless check for an infinite number --- src/testing/core/mocking/mockLink.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/core/mocking/mockLink.ts b/src/testing/core/mocking/mockLink.ts index 6980ce4379f..8bd1405bb24 100644 --- a/src/testing/core/mocking/mockLink.ts +++ b/src/testing/core/mocking/mockLink.ts @@ -130,7 +130,7 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")} } else { if (!response.reuse) { mockedResponses.splice(responseIndex, 1); - } else if (response.reuse !== Number.POSITIVE_INFINITY) { + } else { response.reuse--; } From dd3f59b3b14f6174d60f3ee5a8b2846845c4465b Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Wed, 30 Aug 2023 13:53:46 +0200 Subject: [PATCH 05/14] Split mock reuse tests --- src/testing/core/mocking/mockLink.ts | 2 - .../react/__tests__/MockedProvider.test.tsx | 166 ++++++++++++------ 2 files changed, 111 insertions(+), 57 deletions(-) diff --git a/src/testing/core/mocking/mockLink.ts b/src/testing/core/mocking/mockLink.ts index 8bd1405bb24..54c43ce1fe2 100644 --- a/src/testing/core/mocking/mockLink.ts +++ b/src/testing/core/mocking/mockLink.ts @@ -133,8 +133,6 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")} } else { response.reuse--; } - - const { newData } = response; if (newData) { response.result = newData(); diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index d6f0f1aea35..74d920fc54b 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import { DocumentNode } from "graphql"; -import { render, waitFor } from "@testing-library/react"; +import { act, render, waitFor } from "@testing-library/react"; import gql from "graphql-tag"; import { itAsync, MockedResponse, MockLink } from "../../core"; @@ -8,6 +8,7 @@ import { MockedProvider } from "../MockedProvider"; import { useQuery } from "../../../react/hooks"; import { InMemoryCache } from "../../../cache"; import { ApolloLink } from "../../../link/core"; +import { QueryResult } from "../../../react/types/types"; const variables = { username: "mock_username", @@ -55,6 +56,10 @@ interface Data { }; } +interface Result { + current: QueryResult | null +} + interface Variables { username: string; } @@ -481,20 +486,34 @@ describe("General use", () => { expect(errorThrown).toBeFalsy(); }); - it('should reuse mocks when instructed to', async () => { - let isLoaded = false; - let isError = false; + it('reuses a mock a configured number of times when `reuse` is configured', async () => { + const result: Result = { current: null }; function Component({ username }: Variables) { - const { loading, error } = useQuery(query, { variables: { username }, fetchPolicy: 'network-only' }); - if (!loading) { - if (error) { - isError = true; - } - isLoaded = true; - } + result.current = useQuery(query, { variables: { username } }); return null; } + const waitForLoaded = async () => { + await waitFor(() => { + expect(result.current?.loading).toBe(false); + expect(result.current?.error).toBeUndefined(); + }); + } + + const waitForError = async () => { + await waitFor(() => { + expect(result.current?.error?.message).toMatch(/No more mocked responses/); + }); + } + + const refetch = () => { + return act(async () => { + try { + await result.current?.refetch(); + } catch { } + }); + } + const mocks: ReadonlyArray = [ { request: { @@ -506,78 +525,115 @@ describe("General use", () => { reuse: 1, result: { data: { user } } }, + ]; + + const mockLink = new MockLink(mocks, true, { showWarnings: false }); + const link = ApolloLink.from([errorLink, mockLink]); + const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + + render(, { wrapper: Wrapper }); + await waitForLoaded(); + await refetch(); + await waitForLoaded(); + await refetch(); + await waitForError(); + }); + + it('reuses a mock infinite number of times when `reuse` is configured with Number.POSITIVE_INFINITY', async () => { + const result: Result = { current: null }; + function Component({ username }: Variables) { + result.current = useQuery(query, { variables: { username } }); + return null; + } + + const waitForLoaded = async () => { + await waitFor(() => { + expect(result.current?.loading).toBe(false); + expect(result.current?.error).toBeUndefined(); + }); + } + + const refetch = () => { + return act(async () => { + try { + await result.current?.refetch(); + } catch { } + }); + } + + const mocks: ReadonlyArray = [ { request: { query, variables: { - username: 'mock_username2' + username: 'mock_username' } }, reuse: Number.POSITIVE_INFINITY, result: { data: { user } } }, - { - request: { - query, - variables: { - username: 'mock_username3' - } - }, - result: { data: { user } } - } ]; const mockLink = new MockLink(mocks, true, { showWarnings: false }); + const link = ApolloLink.from([errorLink, mockLink]); + const Wrapper = ({ children }: { children: React.ReactNode }) => {children} - const Wrapper = ({ children }: { children: React.ReactNode }) => {children} - const reset = () => { - isLoaded = false; - isError = false; + render(, { wrapper: Wrapper }); + await waitForLoaded(); + await refetch(); + await waitForLoaded(); + await refetch(); + await waitForLoaded(); + }); + + it('uses a mock once when `reuse` is not configured', async () => { + const result: Result = { current: null }; + function Component({ username }: Variables) { + result.current = useQuery(query, { variables: { username } }); + return null; } + const waitForLoaded = async () => { await waitFor(() => { - expect(isLoaded).toBe(true); - expect(isError).toBe(false); + expect(result.current?.loading).toBe(false); + expect(result.current?.error).toBeUndefined(); }); } + const waitForError = async () => { await waitFor(() => { - expect(isLoaded).toBe(true); - expect(isError).toBe(true); + expect(result.current?.error?.message).toMatch(/No more mocked responses/); }); } - const { rerender } = render(, { wrapper: Wrapper }); - await waitForLoaded(); - reset(); - - rerender(); - await waitForLoaded(); - reset(); + const refetch = () => { + return act(async () => { + try { + await result.current?.refetch(); + } catch { } + }); + } - rerender(); - await waitForLoaded(); - reset(); + const mocks: ReadonlyArray = [ + { + request: { + query, + variables: { + username: 'mock_username' + } + }, + result: { data: { user } } + }, + ]; - rerender(); - await waitForLoaded(); - reset(); + const mockLink = new MockLink(mocks, true, { showWarnings: false }); + const link = ApolloLink.from([errorLink, mockLink]); + const Wrapper = ({ children }: { children: React.ReactNode }) => {children} - rerender(); + render(, { wrapper: Wrapper }); await waitForLoaded(); - reset(); - - rerender(); - await waitForError(); - reset(); - - rerender(); + await refetch(); await waitForError(); - reset(); - - rerender(); - await waitForLoaded(); - reset(); }); it('should return "Mocked response should contain" errors in response', async () => { From 1ea0dab2f1aaccf4b0766fa23bbd31a064782bfe Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Wed, 30 Aug 2023 13:54:14 +0200 Subject: [PATCH 06/14] Add additional mock re-use test --- .../react/__tests__/MockedProvider.test.tsx | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index 74d920fc54b..21f3d05a743 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -636,6 +636,70 @@ describe("General use", () => { await waitForError(); }); + it('can still use other mocks after a mock has been fully consumed', async () => { + const result: Result = { current: null }; + function Component({ username }: Variables) { + result.current = useQuery(query, { variables: { username } }); + return null; + } + + const waitForLoaded = async () => { + await waitFor(() => { + expect(result.current?.loading).toBe(false); + expect(result.current?.error).toBeUndefined(); + }); + } + + const refetch = () => { + return act(async () => { + try { + await result.current?.refetch(); + } catch { } + }); + } + + const mocks: ReadonlyArray = [ + { + request: { + query, + variables: { + username: 'mock_username' + } + }, + reuse: 1, + result: { data: { user } } + }, + { + request: { + query, + variables: { + username: 'mock_username' + } + }, + result: { + data: { + user: { + __typename: 'User', + id: 'new_id' + } + } + } + }, + ]; + + const mockLink = new MockLink(mocks, true, { showWarnings: false }); + const link = ApolloLink.from([errorLink, mockLink]); + const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + + render(, { wrapper: Wrapper }); + await waitForLoaded(); + await refetch(); + await waitForLoaded(); + await refetch(); + await waitForLoaded(); + expect(result.current?.data?.user).toEqual({ __typename: 'User', id: 'new_id' }); + }); + it('should return "Mocked response should contain" errors in response', async () => { let finished = false; function Component({ ...variables }: Variables) { From 591b71105dc873d29cf874d199857d35e6159a1f Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Wed, 30 Aug 2023 14:11:44 +0200 Subject: [PATCH 07/14] Rename reuse -> maxUsageCount --- docs/source/development-testing/testing.mdx | 4 ++-- src/testing/core/mocking/mockLink.ts | 16 ++++++++++++---- .../react/__tests__/MockedProvider.test.tsx | 12 ++++++------ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/source/development-testing/testing.mdx b/docs/source/development-testing/testing.mdx index f9a07d9e5a3..79e55c1bbbe 100644 --- a/docs/source/development-testing/testing.mdx +++ b/docs/source/development-testing/testing.mdx @@ -152,7 +152,7 @@ it("renders without error", async () => { #### Reusing mocks -By default, a mock is only used once. If you want to reuse a mock for multiple operations, you can set the `reuse` field to a number indicating how many times the mock should be reused: +By default, a mock is only used once. If you want to reuse a mock for multiple operations, you can set the `maxUsageCount` field to a number indicating how many times the mock should be used: @@ -172,7 +172,7 @@ const mocks = [ dog: { id: "1", name: "Buck", breed: "bulldog" } } }, - reuse: 2, // The mock can be reused twice before it's removed, default is 0 + maxUsageCount: 2, // The mock can be used twice before it's removed, default is 1 } ]; ``` diff --git a/src/testing/core/mocking/mockLink.ts b/src/testing/core/mocking/mockLink.ts index 54c43ce1fe2..2940d73ea47 100644 --- a/src/testing/core/mocking/mockLink.ts +++ b/src/testing/core/mocking/mockLink.ts @@ -27,7 +27,7 @@ export interface MockedResponse< > { request: GraphQLRequest; result?: FetchResult | ResultFunction>; - reuse?: number; + maxUsageCount?: number; error?: Error; delay?: number; newData?: ResultFunction; @@ -128,10 +128,10 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")} ); } } else { - if (!response.reuse) { - mockedResponses.splice(responseIndex, 1); + if (response.maxUsageCount! > 1) { + response.maxUsageCount!--; } else { - response.reuse--; + mockedResponses.splice(responseIndex, 1); } const { newData } = response; if (newData) { @@ -199,6 +199,14 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")} if (query) { newMockedResponse.request.query = query; } + + mockedResponse.maxUsageCount = mockedResponse.maxUsageCount ?? 1; + invariant( + mockedResponse.maxUsageCount > 0, + `Mock response maxUsageCount must be greater than 0, %s given`, + mockedResponse.maxUsageCount + ); + return newMockedResponse; } } diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index 21f3d05a743..bd1156b42a3 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -486,7 +486,7 @@ describe("General use", () => { expect(errorThrown).toBeFalsy(); }); - it('reuses a mock a configured number of times when `reuse` is configured', async () => { + it('Uses a mock a configured number of times when `maxUsageCount` is configured', async () => { const result: Result = { current: null }; function Component({ username }: Variables) { result.current = useQuery(query, { variables: { username } }); @@ -522,7 +522,7 @@ describe("General use", () => { username: 'mock_username' } }, - reuse: 1, + maxUsageCount: 2, result: { data: { user } } }, ]; @@ -539,7 +539,7 @@ describe("General use", () => { await waitForError(); }); - it('reuses a mock infinite number of times when `reuse` is configured with Number.POSITIVE_INFINITY', async () => { + it('Uses a mock infinite number of times when `maxUsageCount` is configured with Number.POSITIVE_INFINITY', async () => { const result: Result = { current: null }; function Component({ username }: Variables) { result.current = useQuery(query, { variables: { username } }); @@ -569,7 +569,7 @@ describe("General use", () => { username: 'mock_username' } }, - reuse: Number.POSITIVE_INFINITY, + maxUsageCount: Number.POSITIVE_INFINITY, result: { data: { user } } }, ]; @@ -586,7 +586,7 @@ describe("General use", () => { await waitForLoaded(); }); - it('uses a mock once when `reuse` is not configured', async () => { + it('uses a mock once when `maxUsageCount` is not configured', async () => { const result: Result = { current: null }; function Component({ username }: Variables) { result.current = useQuery(query, { variables: { username } }); @@ -666,7 +666,7 @@ describe("General use", () => { username: 'mock_username' } }, - reuse: 1, + maxUsageCount: 2, result: { data: { user } } }, { From 4b2d3c96cb2064e25680720ac4a1d1aa11c0383c Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 30 Aug 2023 11:45:52 -0600 Subject: [PATCH 08/14] Run prettier on changes to MockedProvider test --- .../react/__tests__/MockedProvider.test.tsx | 121 +++++++++++------- 1 file changed, 72 insertions(+), 49 deletions(-) diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index bd1156b42a3..0330f7e6a91 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -57,7 +57,7 @@ interface Data { } interface Result { - current: QueryResult | null + current: QueryResult | null; } interface Variables { @@ -486,10 +486,12 @@ describe("General use", () => { expect(errorThrown).toBeFalsy(); }); - it('Uses a mock a configured number of times when `maxUsageCount` is configured', async () => { + it("Uses a mock a configured number of times when `maxUsageCount` is configured", async () => { const result: Result = { current: null }; function Component({ username }: Variables) { - result.current = useQuery(query, { variables: { username } }); + result.current = useQuery(query, { + variables: { username }, + }); return null; } @@ -498,38 +500,42 @@ describe("General use", () => { expect(result.current?.loading).toBe(false); expect(result.current?.error).toBeUndefined(); }); - } + }; const waitForError = async () => { await waitFor(() => { - expect(result.current?.error?.message).toMatch(/No more mocked responses/); + expect(result.current?.error?.message).toMatch( + /No more mocked responses/ + ); }); - } + }; const refetch = () => { return act(async () => { try { await result.current?.refetch(); - } catch { } + } catch {} }); - } + }; const mocks: ReadonlyArray = [ { request: { query, variables: { - username: 'mock_username' - } + username: "mock_username", + }, }, maxUsageCount: 2, - result: { data: { user } } + result: { data: { user } }, }, ]; const mockLink = new MockLink(mocks, true, { showWarnings: false }); const link = ApolloLink.from([errorLink, mockLink]); - const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + const Wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); render(, { wrapper: Wrapper }); await waitForLoaded(); @@ -539,10 +545,12 @@ describe("General use", () => { await waitForError(); }); - it('Uses a mock infinite number of times when `maxUsageCount` is configured with Number.POSITIVE_INFINITY', async () => { + it("Uses a mock infinite number of times when `maxUsageCount` is configured with Number.POSITIVE_INFINITY", async () => { const result: Result = { current: null }; function Component({ username }: Variables) { - result.current = useQuery(query, { variables: { username } }); + result.current = useQuery(query, { + variables: { username }, + }); return null; } @@ -551,32 +559,34 @@ describe("General use", () => { expect(result.current?.loading).toBe(false); expect(result.current?.error).toBeUndefined(); }); - } + }; const refetch = () => { return act(async () => { try { await result.current?.refetch(); - } catch { } + } catch {} }); - } + }; const mocks: ReadonlyArray = [ { request: { query, variables: { - username: 'mock_username' - } + username: "mock_username", + }, }, maxUsageCount: Number.POSITIVE_INFINITY, - result: { data: { user } } + result: { data: { user } }, }, ]; const mockLink = new MockLink(mocks, true, { showWarnings: false }); const link = ApolloLink.from([errorLink, mockLink]); - const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + const Wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); render(, { wrapper: Wrapper }); await waitForLoaded(); @@ -586,10 +596,12 @@ describe("General use", () => { await waitForLoaded(); }); - it('uses a mock once when `maxUsageCount` is not configured', async () => { + it("uses a mock once when `maxUsageCount` is not configured", async () => { const result: Result = { current: null }; function Component({ username }: Variables) { - result.current = useQuery(query, { variables: { username } }); + result.current = useQuery(query, { + variables: { username }, + }); return null; } @@ -598,37 +610,41 @@ describe("General use", () => { expect(result.current?.loading).toBe(false); expect(result.current?.error).toBeUndefined(); }); - } + }; const waitForError = async () => { await waitFor(() => { - expect(result.current?.error?.message).toMatch(/No more mocked responses/); + expect(result.current?.error?.message).toMatch( + /No more mocked responses/ + ); }); - } + }; const refetch = () => { return act(async () => { try { await result.current?.refetch(); - } catch { } + } catch {} }); - } + }; const mocks: ReadonlyArray = [ { request: { query, variables: { - username: 'mock_username' - } + username: "mock_username", + }, }, - result: { data: { user } } + result: { data: { user } }, }, ]; const mockLink = new MockLink(mocks, true, { showWarnings: false }); const link = ApolloLink.from([errorLink, mockLink]); - const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + const Wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); render(, { wrapper: Wrapper }); await waitForLoaded(); @@ -636,10 +652,12 @@ describe("General use", () => { await waitForError(); }); - it('can still use other mocks after a mock has been fully consumed', async () => { + it("can still use other mocks after a mock has been fully consumed", async () => { const result: Result = { current: null }; function Component({ username }: Variables) { - result.current = useQuery(query, { variables: { username } }); + result.current = useQuery(query, { + variables: { username }, + }); return null; } @@ -648,48 +666,50 @@ describe("General use", () => { expect(result.current?.loading).toBe(false); expect(result.current?.error).toBeUndefined(); }); - } + }; const refetch = () => { return act(async () => { try { await result.current?.refetch(); - } catch { } + } catch {} }); - } + }; const mocks: ReadonlyArray = [ { request: { query, variables: { - username: 'mock_username' - } + username: "mock_username", + }, }, maxUsageCount: 2, - result: { data: { user } } + result: { data: { user } }, }, { request: { query, variables: { - username: 'mock_username' - } + username: "mock_username", + }, }, result: { data: { user: { - __typename: 'User', - id: 'new_id' - } - } - } + __typename: "User", + id: "new_id", + }, + }, + }, }, ]; const mockLink = new MockLink(mocks, true, { showWarnings: false }); const link = ApolloLink.from([errorLink, mockLink]); - const Wrapper = ({ children }: { children: React.ReactNode }) => {children} + const Wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); render(, { wrapper: Wrapper }); await waitForLoaded(); @@ -697,7 +717,10 @@ describe("General use", () => { await waitForLoaded(); await refetch(); await waitForLoaded(); - expect(result.current?.data?.user).toEqual({ __typename: 'User', id: 'new_id' }); + expect(result.current?.data?.user).toEqual({ + __typename: "User", + id: "new_id", + }); }); it('should return "Mocked response should contain" errors in response', async () => { From bfb73009838eda741dbe2cc6fcaa9a552f712ce5 Mon Sep 17 00:00:00 2001 From: Seba Kerckhof Date: Tue, 19 Sep 2023 23:05:16 +0200 Subject: [PATCH 09/14] Improve infinite reuse test --- src/testing/react/__tests__/MockedProvider.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index 1d666f9d676..b1676890bf7 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -719,10 +719,10 @@ describe("General use", () => { ); render(, { wrapper: Wrapper }); - await waitForLoaded(); - await refetch(); - await waitForLoaded(); - await refetch(); + for (let i = 0; i < 100; i++) { + await waitForLoaded(); + await refetch(); + } await waitForLoaded(); }); From 0a58809ed593f7233b88ace2203ecebbcc7260cc Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Thu, 21 Sep 2023 14:43:40 -0700 Subject: [PATCH 10/14] chore: remove graphql 14 from peerDependencies in lockfile --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6d8d664607d..8a5038d6d4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,7 +103,7 @@ "npm": "^7.20.3 || ^8.0.0 || ^9.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql": "^15.0.0 || ^16.0.0", "graphql-ws": "^5.5.5", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", From bc30689b7b57f8a21e5ef3a2526b17880e12a42d Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Thu, 21 Sep 2023 14:49:21 -0700 Subject: [PATCH 11/14] chore: update API reports --- .api-reports/api-report-react.md | 25 ++++++++++++++++++++++++ .api-reports/api-report-react_hooks.md | 27 ++++++++++++++++++++++++++ .api-reports/api-report.md | 25 ++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index e833fc3146b..1bb039c96dd 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -350,6 +350,8 @@ export interface BackgroundQueryHookOptions { variables?: TVariables; } +// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export class SuspenseCache extends SuspenseCache_2 { + constructor(); +} + +// @public (undocumented) +class SuspenseCache_2 { + // Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts + constructor(options?: SuspenseCacheOptions); + // (undocumented) + getQueryRef(cacheKey: CacheKey, createObservable: () => ObservableQuery): InternalQueryReference; +} + +// @public (undocumented) +interface SuspenseCacheOptions { + // (undocumented) + autoDisposeTimeoutMs?: number; +} + // @public (undocumented) export type SuspenseQueryHookFetchPolicy = Extract; @@ -1926,6 +1949,8 @@ export interface SuspenseQueryHookOptions { variables?: TVariables; } +// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +class SuspenseCache extends SuspenseCache_2 { + constructor(); +} + +// @public (undocumented) +class SuspenseCache_2 { + // Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts + constructor(options?: SuspenseCacheOptions); + // (undocumented) + getQueryRef(cacheKey: CacheKey, createObservable: () => ObservableQuery): InternalQueryReference; +} + +// @public (undocumented) +interface SuspenseCacheOptions { + // (undocumented) + autoDisposeTimeoutMs?: number; +} + // @public (undocumented) type SuspenseQueryHookFetchPolicy = Extract; @@ -1807,6 +1832,8 @@ interface SuspenseQueryHookOptions { variables?: TVariables; } +// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export class SuspenseCache extends SuspenseCache_2 { + constructor(); +} + +// @public (undocumented) +class SuspenseCache_2 { + // Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts + constructor(options?: SuspenseCacheOptions); + // (undocumented) + getQueryRef(cacheKey: CacheKey, createObservable: () => ObservableQuery): InternalQueryReference; +} + +// @public (undocumented) +interface SuspenseCacheOptions { + // (undocumented) + autoDisposeTimeoutMs?: number; +} + // @public (undocumented) export type SuspenseQueryHookFetchPolicy = Extract; @@ -2538,6 +2561,8 @@ export interface SuspenseQueryHookOptions Date: Thu, 21 Sep 2023 14:53:27 -0700 Subject: [PATCH 12/14] chore: revert old API reports changes --- .api-reports/api-report-react.md | 25 ----------------------- .api-reports/api-report-react_hooks.md | 27 ------------------------- .api-reports/api-report-testing.md | 2 ++ .api-reports/api-report-testing_core.md | 2 ++ .api-reports/api-report.md | 25 ----------------------- 5 files changed, 4 insertions(+), 77 deletions(-) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 1bb039c96dd..e833fc3146b 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -350,8 +350,6 @@ export interface BackgroundQueryHookOptions { variables?: TVariables; } -// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export class SuspenseCache extends SuspenseCache_2 { - constructor(); -} - -// @public (undocumented) -class SuspenseCache_2 { - // Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts - constructor(options?: SuspenseCacheOptions); - // (undocumented) - getQueryRef(cacheKey: CacheKey, createObservable: () => ObservableQuery): InternalQueryReference; -} - -// @public (undocumented) -interface SuspenseCacheOptions { - // (undocumented) - autoDisposeTimeoutMs?: number; -} - // @public (undocumented) export type SuspenseQueryHookFetchPolicy = Extract; @@ -1949,8 +1926,6 @@ export interface SuspenseQueryHookOptions { variables?: TVariables; } -// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -class SuspenseCache extends SuspenseCache_2 { - constructor(); -} - -// @public (undocumented) -class SuspenseCache_2 { - // Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts - constructor(options?: SuspenseCacheOptions); - // (undocumented) - getQueryRef(cacheKey: CacheKey, createObservable: () => ObservableQuery): InternalQueryReference; -} - -// @public (undocumented) -interface SuspenseCacheOptions { - // (undocumented) - autoDisposeTimeoutMs?: number; -} - // @public (undocumented) type SuspenseQueryHookFetchPolicy = Extract; @@ -1832,8 +1807,6 @@ interface SuspenseQueryHookOptions, TVariables = Record // (undocumented) error?: Error; // (undocumented) + maxUsageCount?: number; + // (undocumented) newData?: ResultFunction; // (undocumented) request: GraphQLRequest; diff --git a/.api-reports/api-report-testing_core.md b/.api-reports/api-report-testing_core.md index 3cf490dd0d3..dafb615346b 100644 --- a/.api-reports/api-report-testing_core.md +++ b/.api-reports/api-report-testing_core.md @@ -844,6 +844,8 @@ export interface MockedResponse, TVariables = Record // (undocumented) error?: Error; // (undocumented) + maxUsageCount?: number; + // (undocumented) newData?: ResultFunction; // (undocumented) request: GraphQLRequest; diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 8c933201f08..1fac5b82bf4 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -321,8 +321,6 @@ export interface BackgroundQueryHookOptions { variables?: TVariables; } -// Warning: (ae-forgotten-export) The symbol "SuspenseCache_2" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export class SuspenseCache extends SuspenseCache_2 { - constructor(); -} - -// @public (undocumented) -class SuspenseCache_2 { - // Warning: (ae-forgotten-export) The symbol "SuspenseCacheOptions" needs to be exported by the entry point index.d.ts - constructor(options?: SuspenseCacheOptions); - // (undocumented) - getQueryRef(cacheKey: CacheKey, createObservable: () => ObservableQuery): InternalQueryReference; -} - -// @public (undocumented) -interface SuspenseCacheOptions { - // (undocumented) - autoDisposeTimeoutMs?: number; -} - // @public (undocumented) export type SuspenseQueryHookFetchPolicy = Extract; @@ -2561,8 +2538,6 @@ export interface SuspenseQueryHookOptions Date: Thu, 21 Sep 2023 14:55:07 -0700 Subject: [PATCH 13/14] chore: updates extract-api script to build library first --- .github/workflows/api-extractor.yml | 4 +--- package.json | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/api-extractor.yml b/.github/workflows/api-extractor.yml index 5b232f45133..e8d0f5b06d6 100644 --- a/.github/workflows/api-extractor.yml +++ b/.github/workflows/api-extractor.yml @@ -19,8 +19,6 @@ jobs: - name: Install dependencies (with cache) uses: bahmutov/npm-install@v1 - - name: Run build - run: npm run build - + # Builds the library and runs the api extractor - name: Run Api-Extractor run: npm run extract-api diff --git a/package.json b/package.json index 831ed6e9419..dda56091ebd 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "prepdist": "node ./config/prepareDist.js", "prepdist:changesets": "ts-node-script config/prepareChangesetsRelease.ts", "postprocess-dist": "ts-node-script config/postprocessDist.ts", - "extract-api": "ts-node-script config/apiExtractor.ts", + "extract-api": "npm run build && ts-node-script config/apiExtractor.ts", "clean": "rimraf dist coverage lib temp", "check:format": "prettier --check .", "ci:precheck": "node config/precheck.js", From fd5b55fde987f10b5c6866e1d47d9b312a614bc9 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Thu, 21 Sep 2023 14:57:24 -0700 Subject: [PATCH 14/14] chore: update .size-limit.cjs --- .size-limit.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.cjs b/.size-limit.cjs index ff6372f7591..090bc4c9dc4 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -1,7 +1,7 @@ const checks = [ { path: "dist/apollo-client.min.cjs", - limit: "37986", + limit: "38000", }, { path: "dist/main.cjs",