diff --git a/package.json b/package.json index 9ac3d106..f34f542f 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "depcheck": "^1.4.1", "jest": "^26.4.2", "lerna": "^3.22.1", + "mock-apollo-client": "^1.1.0", "mongoose": "^5.10.16", "operation-name-mock-link": "^0.0.6", "react": "^17.0.1", diff --git a/packages/react-hooks/multi.ts b/packages/react-hooks/multi.ts index f80e41a0..ea8d53e0 100644 --- a/packages/react-hooks/multi.ts +++ b/packages/react-hooks/multi.ts @@ -152,10 +152,12 @@ const buildMultiResult = ( // workaround for https://github.com/apollographql/apollo-client/issues/2810 const graphQLErrors = get(queryResult, "error.networkError.result.errors"); - const { refetch, networkStatus, error, fetchMore, data } = queryResult; + const { refetch, networkStatus, error, fetchMore, data, loading, variables } = + queryResult; // Note: Scalar types like Dates are NOT converted. It should be done at the UI level. - const documents = data?.[resolverName]?.results; - const totalCount = data?.[resolverName]?.totalCount; + // We are foreced to recast because resolverName is dynamic, so we cannot type "data" correctly yet + const documents = data?.[resolverName]?.results as Array | undefined; + const totalCount = data?.[resolverName]?.totalCount as number | undefined; // see https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/networkStatus.ts const loadingInitial = networkStatus === 1; const loadingMore = networkStatus === 3 || networkStatus === 2; @@ -178,16 +180,28 @@ const buildMultiResult = ( * @param providedInput */ loadMore() { + if (!documents) { + if (loading) { + throw new Error( + "Called loadMore while documents were still loading. Please wait for the first documents to be loaded before loading more" + ); + } else { + throw new Error( + "No 'documents' were returned by initial query (it probably failed with an error), impossible to call loadMore" + ); + } + } // get terms passed as argument or else just default to incrementing the offset if (options.pollInterval) throw new Error("Can't call loadMore when polling is set."); - const offsetInput = { - ...paginationInput, - offset: documents.length, - }; + const offsetVariables = merge({}, variables, { + input: { + offset: documents.length, + }, + }); return fetchMore({ - variables: { input: offsetInput }, + variables: offsetVariables, updateQuery: fetchMoreUpdateQuery(resolverName), }); }, @@ -199,7 +213,8 @@ const buildMultiResult = ( }; interface UseMultiOptions - extends QueryHookOptions { + // we support pollInterval at the root as a legacy behaviour + extends Pick, "pollInterval"> { model: VulcanGraphqlModel; input?: MultiInput; fragment?: string | DocumentNode; diff --git a/packages/react-hooks/test/queries.test.tsx b/packages/react-hooks/test/queries.test.tsx index c2de7c76..9ea6312d 100644 --- a/packages/react-hooks/test/queries.test.tsx +++ b/packages/react-hooks/test/queries.test.tsx @@ -11,6 +11,9 @@ import { renderHook, act } from "@testing-library/react-hooks"; import { VulcanGraphqlModel } from "@vulcanjs/graphql"; import { createGraphqlModel } from "@vulcanjs/graphql/extendModel"; +import { VulcanDocument } from "@vulcanjs/schema"; + +import { createMockClient } from "mock-apollo-client"; describe("react-hooks/queries", function () { const typeName = "Foo"; @@ -32,6 +35,10 @@ describe("react-hooks/queries", function () { multiTypeName, }, }); + interface FooType extends VulcanDocument { + id: string; + hello: string; + } const fragment = Foo.graphql.defaultFragment; const fragmentName = Foo.graphql.defaultFragmentName; @@ -187,8 +194,13 @@ describe("react-hooks/queries", function () { request: { query: defaultQuery, variables: { - // get an offset to load only relevant data - input: { limit: 1, offset: 1 }, + ...defaultVariables, + input: { + ...defaultVariables?.input, + limit: 1, + // get an offset to load only relevant data + offset: 1, + }, }, }, result: { @@ -205,8 +217,13 @@ describe("react-hooks/queries", function () { request: { query: defaultQuery, variables: { - // get an offset to load only relevant data - input: { limit: 1, offset: 2 }, + ...defaultVariables, + input: { + ...defaultVariables?.input, + limit: 1, + // get an offset to load only relevant data + offset: 2, + }, }, }, result: { @@ -264,5 +281,46 @@ describe("react-hooks/queries", function () { // await waitForNextUpdate(); expect(queryResult.documents).toEqual([fooWithTypename, fooWithTypename]); }); + test("loadMore respects filters", async () => { + const apolloClient = createMockClient(); + const onRequest = jest.fn().mockResolvedValue({ + data: { + foos: { + results: [], + totalCount: 10, + }, + }, + }); + apolloClient.setRequestHandler(defaultQuery, onRequest); + + const { result, waitForValueToChange } = renderHook( + () => + useMulti({ + model: Foo, + input: { limit: 1, filter: { hello: { _eq: "world" } } }, + queryOptions: { + client: apolloClient, + }, + }) + //{ wrapper } + ); + // wait for loading to be done + await waitForValueToChange(() => result.current.loading); + let queryResult = result.current; + await act(async () => { + await queryResult.loadMore(); + }); + expect(onRequest).toHaveBeenCalledTimes(2); + expect(onRequest.mock.calls[1][0]).toMatchObject({ + input: { + filter: { + hello: { + _eq: "world", + }, + }, + limit: 1, + }, + }); + }); }); }); diff --git a/yarn.lock b/yarn.lock index fa2fb75d..4f1f0fad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10539,6 +10539,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: dependencies: minimist "^1.2.5" +mock-apollo-client@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-1.1.0.tgz#0589ae458a2539518e870bb0c08961f30a9eaec1" + integrity sha512-OXCvwAwwHbieMMipcE3wGdPONPHC+f65EEiyC1XpYaS5Jk6/c7oBe9t8knwzAqyCQ9nZziHdR8UDqORPTfkcFw== + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"