From 67a6a4267a5e92fc7dcc8dae41fbb1cf862408f9 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 5 Dec 2024 14:08:58 +0100 Subject: [PATCH] additional adjustments --- .vscode/settings.json | 10 +- src/__tests__/client.ts | 80 ++- src/__tests__/fetchMore.ts | 4 +- src/__tests__/mutationResults.ts | 751 ++++++++++------------- src/cache/inmemory/__tests__/policies.ts | 316 +++++----- src/core/__tests__/ObservableQuery.ts | 113 ++-- src/core/__tests__/QueryManager/index.ts | 6 +- src/core/__tests__/fetchPolicies.ts | 14 +- 8 files changed, 586 insertions(+), 708 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index dddb0e3d487..bb301d08fce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,14 @@ // Place your settings in this file to overwrite default and user settings. { "editor.tabSize": 2, - "editor.rulers": [80], + "editor.rulers": [ + 80 + ], "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "typescript.tsdk": "node_modules/typescript/lib", - "cSpell.enableFiletypes": ["mdx"], - "jest.jestCommandLine": "node_modules/.bin/jest --config ./config/jest.config.js --ignoreProjects 'ReactDOM 17' --runInBand" + "cSpell.enableFiletypes": [ + "mdx" + ], + "jest.jestCommandLine": "node --expose-gc node_modules/.bin/jest --config ./config/jest.config.js --ignoreProjects 'ReactDOM 17' --runInBand" } diff --git a/src/__tests__/client.ts b/src/__tests__/client.ts index d4f2fe0858d..686d1c078ee 100644 --- a/src/__tests__/client.ts +++ b/src/__tests__/client.ts @@ -72,19 +72,19 @@ describe("client", () => { }); expect(() => { - client.query( + void client.query( gql` { a } ` as any ); - }).toThrowError( + }).toThrow( "query option is required. You must specify your GraphQL document in the query option." ); expect(() => { - client.query({ query: "{ a }" } as any); - }).toThrowError('You must wrap the query string in a "gql" tag.'); + void client.query({ query: "{ a }" } as any); + }).toThrow('You must wrap the query string in a "gql" tag.'); }); it("should throw an error if mutation option is missing", async () => { @@ -137,48 +137,44 @@ describe("client", () => { } ); - itAsync( - "should allow a single query with an apollo-link enabled network interface", - (resolve, reject) => { - const query = gql` - query people { - allPeople(first: 1) { - people { - name - __typename - } + it("should allow a single query with an apollo-link enabled network interface", async () => { + const query = gql` + query people { + allPeople(first: 1) { + people { + name __typename } + __typename } - `; + } + `; - const data = { - allPeople: { - people: [ - { - name: "Luke Skywalker", - __typename: "Person", - }, - ], - __typename: "People", - }, - }; + const data = { + allPeople: { + people: [ + { + name: "Luke Skywalker", + __typename: "Person", + }, + ], + __typename: "People", + }, + }; - const variables = { first: 1 }; + const variables = { first: 1 }; - const link = ApolloLink.from([() => Observable.of({ data })]); + const link = ApolloLink.from([() => Observable.of({ data })]); - const client = new ApolloClient({ - link, - cache: new InMemoryCache({ addTypename: false }), - }); + const client = new ApolloClient({ + link, + cache: new InMemoryCache({ addTypename: false }), + }); - client.query({ query, variables }).then((actualResult) => { - expect(actualResult.data).toEqual(data); - resolve(); - }); - } - ); + const actualResult = await client.query({ query, variables }); + + expect(actualResult.data).toEqual(data); + }); itAsync( "should allow for a single query with complex default variables to take place", @@ -1767,7 +1763,7 @@ describe("client", () => { cache: new InMemoryCache(), }); expect(() => { - client.query({ query, returnPartialData: true } as QueryOptions); + void client.query({ query, returnPartialData: true } as QueryOptions); }).toThrowError(/returnPartialData/); }); @@ -1777,7 +1773,7 @@ describe("client", () => { cache: new InMemoryCache(), }); expect(() => { - client.query({ query, returnPartialData: true } as QueryOptions); + void client.query({ query, returnPartialData: true } as QueryOptions); }).toThrowError(/returnPartialData/); }); }); @@ -2072,7 +2068,7 @@ describe("client", () => { // this write should be completely ignored by the standby query client.writeQuery({ query, data: data2 }); setTimeout(() => { - obs.setOptions({ query, fetchPolicy: "cache-first" }); + void obs.setOptions({ query, fetchPolicy: "cache-first" }); }, 10); await expect(stream).toEmitMatchedValue({ data: data2 }); @@ -3653,7 +3649,7 @@ describe("@connection", () => { // Refetching makes a copy of the current options, which // includes options.nextFetchPolicy, so the inner // nextFetchPolicy function ends up getting called twice. - obs.refetch(); + void obs.refetch(); await expect(stream).toEmitMatchedValue({ data: { count: "initial" } }); expect(nextFetchPolicyCallCount).toBe(2); diff --git a/src/__tests__/fetchMore.ts b/src/__tests__/fetchMore.ts index aa74cecfaee..9d53d363789 100644 --- a/src/__tests__/fetchMore.ts +++ b/src/__tests__/fetchMore.ts @@ -1696,7 +1696,7 @@ describe("fetchMore on an observable query with connection", () => { expect(data.entry.comments.length).toBe(10); } - observable.fetchMore({ + void observable.fetchMore({ variables: { start: 10 }, updateQuery: (prev: any, options: any) => { const state = cloneDeep(prev) as any; @@ -1767,7 +1767,7 @@ describe("fetchMore on an observable query with connection", () => { expect((data as any).entry.comments.length).toBe(10); } - observable.fetchMore({ + void observable.fetchMore({ variables: { start: 10 }, }); diff --git a/src/__tests__/mutationResults.ts b/src/__tests__/mutationResults.ts index 712e3f7cc47..0341220b32d 100644 --- a/src/__tests__/mutationResults.ts +++ b/src/__tests__/mutationResults.ts @@ -2,7 +2,7 @@ import { cloneDeep } from "lodash"; import gql from "graphql-tag"; import { GraphQLError } from "graphql"; -import { ApolloClient, FetchResult } from "../core"; +import { ApolloClient, ApolloError, FetchResult } from "../core"; import { InMemoryCache } from "../cache"; import { ApolloLink } from "../link/core"; import { @@ -217,17 +217,10 @@ describe("mutation results", () => { result: mutationResult, }); - await obsQuery - .result() - .then(() => { - return client.mutate({ mutation }); - }) - .then(() => { - return client.query({ query }); - }) - .then((newResult: any) => { - expect(newResult.data.todoList.todos[0].completed).toBe(true); - }); + await obsQuery.result(); + await client.mutate({ mutation }); + const newResult = await client.query({ query }); + expect(newResult.data.todoList.todos[0].completed).toBe(true); }); it("correctly integrates field changes by default with variables", async () => { @@ -294,30 +287,16 @@ describe("mutation results", () => { notifyOnNetworkStatusChange: false, }); - await new Promise((resolve, reject) => { - let count = 0; - obs.subscribe({ - next: (result) => { - if (count === 0) { - client.mutate({ mutation, variables: { signature: "1234" } }); - expect(result.data!.mini.cover).toBe("image"); - - setTimeout(() => { - if (count === 0) - reject( - new Error("mutate did not re-call observable with next value") - ); - }, 250); - } - if (count === 1) { - expect(result.data!.mini.cover).toBe("image2"); - resolve(); - } - count++; - }, - error: reject, - }); - }); + const stream = new ObservableStream(obs); + { + const result = await stream.takeNext(); + expect(result.data!.mini.cover).toBe("image"); + } + await client.mutate({ mutation, variables: { signature: "1234" } }); + { + const result = await stream.takeNext(); + expect(result.data!.mini.cover).toBe("image2"); + } }); it("should write results to cache according to errorPolicy", async () => { @@ -841,7 +820,6 @@ describe("mutation results", () => { }); it("does not fail if optional query variables are not supplied", async () => { - let subscriptionHandle: Subscription; const mutationWithVars = gql` mutation createTodo($requiredVar: String!, $optionalVar: String) { createTodo(requiredVar: $requiredVar, optionalVar: $optionalVar) { @@ -867,54 +845,42 @@ describe("mutation results", () => { result: mutationResult, }); - await obsQuery - .result() - .then(() => { - // we have to actually subscribe to the query to be able to update it - return new Promise((resolve) => { - const handle = client.watchQuery({ - query, - variables, - }); - subscriptionHandle = handle.subscribe({ - next(res) { - resolve(res); - }, - }); - }); - }) - .then(() => - client.mutate({ - mutation: mutationWithVars, - variables, - updateQueries: { - todoList: (prev, options) => { - const mResult = options.mutationResult as any; - expect(mResult.data.createTodo.id).toBe("99"); - expect(mResult.data.createTodo.text).toBe( - "This one was created with a mutation." - ); - const state = cloneDeep(prev) as any; - state.todoList.todos.unshift(mResult.data.createTodo); - return state; - }, - }, - }) - ) - .then(() => { - return client.query({ query }); - }) - .then((newResult: any) => { - subscriptionHandle.unsubscribe(); + await obsQuery.result(); - // There should be one more todo item than before - expect(newResult.data.todoList.todos.length).toBe(4); + // we have to actually subscribe to the query to be able to update it - // Since we used `prepend` it should be at the front - expect(newResult.data.todoList.todos[0].text).toBe( - "This one was created with a mutation." - ); - }); + const handle = client.watchQuery({ + query, + variables, + }); + const stream = new ObservableStream(handle); + await stream.takeNext(); + + await client.mutate({ + mutation: mutationWithVars, + variables, + updateQueries: { + todoList: (prev, options) => { + const mResult = options.mutationResult as any; + expect(mResult.data.createTodo.id).toBe("99"); + expect(mResult.data.createTodo.text).toBe( + "This one was created with a mutation." + ); + const state = cloneDeep(prev) as any; + state.todoList.todos.unshift(mResult.data.createTodo); + return state; + }, + }, + }); + const newResult = await client.query({ query }); + + // There should be one more todo item than before + expect(newResult.data.todoList.todos.length).toBe(4); + + // Since we used `prepend` it should be at the front + expect(newResult.data.todoList.todos[0].text).toBe( + "This one was created with a mutation." + ); }); it("does not fail if the query did not complete correctly", async () => { @@ -983,91 +949,69 @@ describe("mutation results", () => { result, } ); + const stream = new ObservableStream(obsQuery); + await stream.takeNext(); - await new Promise((resolve, reject) => { - obsQuery.subscribe({ - next() { - client - .mutate({ - mutation, - updateQueries: { - todoList: (prev, options) => { - const mResult = options.mutationResult as any; - const state = cloneDeep(prev) as any; - // It's unfortunate that this function is called at all, but we are removing - // the updateQueries API soon so it won't matter. - state.todoList.todos.unshift( - mResult.data && mResult.data.createTodo - ); - return state; - }, - }, - }) - .then( - () => reject(new Error("Mutation should have failed")), - () => - client.mutate({ - mutation, - updateQueries: { - todoList: (prev, options) => { - const mResult = options.mutationResult as any; - const state = cloneDeep(prev) as any; - state.todoList.todos.unshift(mResult.data.createTodo); - return state; - }, - }, - }) - ) - .then( - () => reject(new Error("Mutation should have failed")), - () => obsQuery.refetch() - ) - .then(resolve, reject); + await expect(() => + client.mutate({ + mutation, + updateQueries: { + todoList: (prev, options) => { + const mResult = options.mutationResult as any; + const state = cloneDeep(prev) as any; + // It's unfortunate that this function is called at all, but we are removing + // the updateQueries API soon so it won't matter. + state.todoList.todos.unshift( + mResult.data && mResult.data.createTodo + ); + return state; + }, }, - }); - }); + }) + ).rejects.toThrow(); + + await expect(() => + client.mutate({ + mutation, + updateQueries: { + todoList: (prev, options) => { + const mResult = options.mutationResult as any; + const state = cloneDeep(prev) as any; + state.todoList.todos.unshift(mResult.data.createTodo); + return state; + }, + }, + }) + ).rejects.toThrow(); + await obsQuery.refetch(); }); it("error handling in reducer functions", async () => { - let subscriptionHandle: Subscription; const { client, obsQuery } = setupObsQuery({ request: { query: mutation }, result: mutationResult, }); - await obsQuery - .result() - .then(() => { - // we have to actually subscribe to the query to be able to update it - return new Promise((resolve) => { - const handle = client.watchQuery({ query }); - subscriptionHandle = handle.subscribe({ - next(res) { - resolve(res); - }, - }); - }); - }) - .then(() => - client.mutate({ - mutation, - updateQueries: { - todoList: () => { - throw new Error(`Hello... It's me.`); - }, + await obsQuery.result(); + + // we have to actually subscribe to the query to be able to update it + + const handle = client.watchQuery({ query }); + const stream = new ObservableStream(handle); + await stream.takeNext(); + + await expect(() => + client.mutate({ + mutation, + updateQueries: { + todoList: () => { + throw new Error(`Hello... It's me.`); }, - }) - ) - .then( - () => { - subscriptionHandle.unsubscribe(); - throw new Error("should have thrown"); }, - (error) => { - subscriptionHandle.unsubscribe(); - expect(error.message).toBe(`Hello... It's me.`); - } - ); + }) + ).rejects.toThrow( + new ApolloError({ networkError: Error(`Hello... It's me.`) }) + ); }); }); @@ -1149,7 +1093,7 @@ describe("mutation results", () => { const stream = new ObservableStream(watchedQuery); - watchedQuery.refetch(variables2); + await watchedQuery.refetch(variables2); { const result = await stream.takeNext(); @@ -1157,7 +1101,7 @@ describe("mutation results", () => { expect(result.data).toEqual({ echo: "b" }); } - client.mutate({ + await client.mutate({ mutation: resetMutation, updateQueries: { Echo: () => { @@ -1220,7 +1164,7 @@ describe("mutation results", () => { } `; - await Promise.all([ + const results = await Promise.all([ client.mutate({ mutation, variables: { a: 1, b: 2 }, @@ -1236,19 +1180,18 @@ describe("mutation results", () => { client.mutate({ mutation, }), - ]).then((results) => { - expect(client.cache.extract()).toEqual({ - ROOT_MUTATION: { - __typename: "Mutation", - }, - }); - expect(results).toEqual([ - { data: { result: "hello" } }, - { data: { result: "world" } }, - { data: { result: "goodbye" } }, - { data: { result: "moon" } }, - ]); + ]); + expect(client.cache.extract()).toEqual({ + ROOT_MUTATION: { + __typename: "Mutation", + }, }); + expect(results).toEqual([ + { data: { result: "hello" } }, + { data: { result: "world" } }, + { data: { result: "goodbye" } }, + { data: { result: "moon" } }, + ]); }); it("allows mutations with default values", async () => { @@ -1300,7 +1243,7 @@ describe("mutation results", () => { } `; - await Promise.all([ + const results = await Promise.all([ client.mutate({ mutation, variables: { a: 1, b: "water" }, @@ -1313,18 +1256,17 @@ describe("mutation results", () => { mutation, variables: { c: 3 }, }), - ]).then((results) => { - expect(client.cache.extract()).toEqual({ - ROOT_MUTATION: { - __typename: "Mutation", - }, - }); - expect(results).toEqual([ - { data: { result: "hello" } }, - { data: { result: "world" } }, - { data: { result: "goodbye" } }, - ]); + ]); + expect(client.cache.extract()).toEqual({ + ROOT_MUTATION: { + __typename: "Mutation", + }, }); + expect(results).toEqual([ + { data: { result: "hello" } }, + { data: { result: "world" } }, + { data: { result: "goodbye" } }, + ]); }); it("will pass null to the network interface when provided", async () => { @@ -1377,7 +1319,7 @@ describe("mutation results", () => { } `; - await Promise.all([ + const results = await Promise.all([ client.mutate({ mutation, variables: { a: 1, b: 2, c: null }, @@ -1390,18 +1332,17 @@ describe("mutation results", () => { mutation, variables: { a: null, b: null, c: null }, }), - ]).then((results) => { - expect(client.cache.extract()).toEqual({ - ROOT_MUTATION: { - __typename: "Mutation", - }, - }); - expect(results).toEqual([ - { data: { result: "hello" } }, - { data: { result: "world" } }, - { data: { result: "moon" } }, - ]); + ]); + expect(client.cache.extract()).toEqual({ + ROOT_MUTATION: { + __typename: "Mutation", + }, }); + expect(results).toEqual([ + { data: { result: "hello" } }, + { data: { result: "world" } }, + { data: { result: "moon" } }, + ]); }); describe("store transaction updater", () => { @@ -1431,77 +1372,63 @@ describe("mutation results", () => { }; it("analogous of ARRAY_INSERT", async () => { - let subscriptionHandle: Subscription; const { client, obsQuery } = setupObsQuery({ request: { query: mutation }, result: mutationResult, }); - await obsQuery - .result() - .then(() => { - // we have to actually subscribe to the query to be able to update it - return new Promise((resolve) => { - const handle = client.watchQuery({ query }); - subscriptionHandle = handle.subscribe({ - next(res) { - resolve(res); - }, - }); - }); - }) - .then(() => - client.mutate({ - mutation, - update: (proxy, mResult: any) => { - expect(mResult.data.createTodo.id).toBe("99"); - expect(mResult.data.createTodo.text).toBe( - "This one was created with a mutation." - ); + await obsQuery.result(); - const id = "TodoList5"; - const fragment = gql` - fragment todoList on TodoList { - todos { - id - text - completed - __typename - } - } - `; + // we have to actually subscribe to the query to be able to update it - const data: any = proxy.readFragment({ id, fragment }); + const handle = client.watchQuery({ query }); + const stream = new ObservableStream(handle); + await stream.takeNext(); + await client.mutate({ + mutation, + update: (proxy, mResult: any) => { + expect(mResult.data.createTodo.id).toBe("99"); + expect(mResult.data.createTodo.text).toBe( + "This one was created with a mutation." + ); - proxy.writeFragment({ - data: { - ...data, - todos: [mResult.data.createTodo, ...data.todos], - }, - id, - fragment, - }); + const id = "TodoList5"; + const fragment = gql` + fragment todoList on TodoList { + todos { + id + text + completed + __typename + } + } + `; + + const data: any = proxy.readFragment({ id, fragment }); + + proxy.writeFragment({ + data: { + ...data, + todos: [mResult.data.createTodo, ...data.todos], }, - }) - ) - .then(() => { - return client.query({ query }); - }) - .then((newResult: any) => { - subscriptionHandle.unsubscribe(); + id, + fragment, + }); + }, + }); - // There should be one more todo item than before - expect(newResult.data.todoList.todos.length).toBe(4); + const newResult = await client.query({ query }); - // Since we used `prepend` it should be at the front - expect(newResult.data.todoList.todos[0].text).toBe( - "This one was created with a mutation." - ); - }); + // There should be one more todo item than before + expect(newResult.data.todoList.todos.length).toBe(4); + + // Since we used `prepend` it should be at the front + expect(newResult.data.todoList.todos[0].text).toBe( + "This one was created with a mutation." + ); }); it("does not fail if optional query variables are not supplied", async () => { - let subscriptionHandle: Subscription; const mutationWithVars = gql` mutation createTodo($requiredVar: String!, $optionalVar: String) { createTodo(requiredVar: $requiredVar, optionalVar: $optionalVar) { @@ -1528,71 +1455,59 @@ describe("mutation results", () => { result: mutationResult, }); - await obsQuery - .result() - .then(() => { - // we have to actually subscribe to the query to be able to update it - return new Promise((resolve) => { - const handle = client.watchQuery({ - query, - variables, - }); - subscriptionHandle = handle.subscribe({ - next(res) { - resolve(res); - }, - }); - }); - }) - .then(() => - client.mutate({ - mutation: mutationWithVars, - variables, - update: (proxy, mResult: any) => { - expect(mResult.data.createTodo.id).toBe("99"); - expect(mResult.data.createTodo.text).toBe( - "This one was created with a mutation." - ); + await obsQuery.result(); - const id = "TodoList5"; - const fragment = gql` - fragment todoList on TodoList { - todos { - id - text - completed - __typename - } - } - `; + // we have to actually subscribe to the query to be able to update it - const data: any = proxy.readFragment({ id, fragment }); + const handle = client.watchQuery({ + query, + variables, + }); + const stream = new ObservableStream(handle); + await stream.takeNext(); - proxy.writeFragment({ - data: { - ...data, - todos: [mResult.data.createTodo, ...data.todos], - }, - id, - fragment, - }); + await client.mutate({ + mutation: mutationWithVars, + variables, + update: (proxy, mResult: any) => { + expect(mResult.data.createTodo.id).toBe("99"); + expect(mResult.data.createTodo.text).toBe( + "This one was created with a mutation." + ); + + const id = "TodoList5"; + const fragment = gql` + fragment todoList on TodoList { + todos { + id + text + completed + __typename + } + } + `; + + const data: any = proxy.readFragment({ id, fragment }); + + proxy.writeFragment({ + data: { + ...data, + todos: [mResult.data.createTodo, ...data.todos], }, - }) - ) - .then(() => { - return client.query({ query }); - }) - .then((newResult: any) => { - subscriptionHandle.unsubscribe(); + id, + fragment, + }); + }, + }); + const newResult = await client.query({ query }); - // There should be one more todo item than before - expect(newResult.data.todoList.todos.length).toBe(4); + // There should be one more todo item than before + expect(newResult.data.todoList.todos.length).toBe(4); - // Since we used `prepend` it should be at the front - expect(newResult.data.todoList.todos[0].text).toBe( - "This one was created with a mutation." - ); - }); + // Since we used `prepend` it should be at the front + expect(newResult.data.todoList.todos[0].text).toBe( + "This one was created with a mutation." + ); }); it("does not make next queries fail if a mutation fails", async () => { @@ -1607,126 +1522,103 @@ describe("mutation results", () => { } ); - await new Promise((resolve, reject) => { - obsQuery.subscribe({ - next() { - client - .mutate({ - mutation, - update: (proxy, mResult: any) => { - expect(mResult.data.createTodo.id).toBe("99"); - expect(mResult.data.createTodo.text).toBe( - "This one was created with a mutation." - ); - - const id = "TodoList5"; - const fragment = gql` - fragment todoList on TodoList { - todos { - id - text - completed - __typename - } - } - `; - - const data: any = proxy.readFragment({ id, fragment }); - - proxy.writeFragment({ - data: { - ...data, - todos: [mResult.data.createTodo, ...data.todos], - }, - id, - fragment, - }); - }, - }) - .then( - () => reject(new Error("Mutation should have failed")), - () => - client.mutate({ - mutation, - update: (proxy, mResult: any) => { - expect(mResult.data.createTodo.id).toBe("99"); - expect(mResult.data.createTodo.text).toBe( - "This one was created with a mutation." - ); - - const id = "TodoList5"; - const fragment = gql` - fragment todoList on TodoList { - todos { - id - text - completed - __typename - } - } - `; - - const data: any = proxy.readFragment({ id, fragment }); - - proxy.writeFragment({ - data: { - ...data, - todos: [mResult.data.createTodo, ...data.todos], - }, - id, - fragment, - }); - }, - }) - ) - .then( - () => reject(new Error("Mutation should have failed")), - () => obsQuery.refetch() - ) - .then(resolve, reject); + const stream = new ObservableStream(obsQuery); + await stream.takeNext(); + + await expect( + client.mutate({ + mutation, + update: (proxy, mResult: any) => { + expect(mResult.data.createTodo.id).toBe("99"); + expect(mResult.data.createTodo.text).toBe( + "This one was created with a mutation." + ); + + const id = "TodoList5"; + const fragment = gql` + fragment todoList on TodoList { + todos { + id + text + completed + __typename + } + } + `; + + const data: any = proxy.readFragment({ id, fragment }); + + proxy.writeFragment({ + data: { + ...data, + todos: [mResult.data.createTodo, ...data.todos], + }, + id, + fragment, + }); }, - }); - }); + }) + ).rejects.toThrow(); + await expect( + client.mutate({ + mutation, + update: (proxy, mResult: any) => { + expect(mResult.data.createTodo.id).toBe("99"); + expect(mResult.data.createTodo.text).toBe( + "This one was created with a mutation." + ); + + const id = "TodoList5"; + const fragment = gql` + fragment todoList on TodoList { + todos { + id + text + completed + __typename + } + } + `; + + const data: any = proxy.readFragment({ id, fragment }); + + proxy.writeFragment({ + data: { + ...data, + todos: [mResult.data.createTodo, ...data.todos], + }, + id, + fragment, + }); + }, + }) + ).rejects.toThrow(); + await obsQuery.refetch(); }); it("error handling in reducer functions", async () => { - let subscriptionHandle: Subscription; const { client, obsQuery } = setupObsQuery({ request: { query: mutation }, result: mutationResult, }); - await obsQuery - .result() - .then(() => { - // we have to actually subscribe to the query to be able to update it - return new Promise((resolve) => { - const handle = client.watchQuery({ query }); - subscriptionHandle = handle.subscribe({ - next(res) { - resolve(res); - }, - }); - }); - }) - .then(() => - client.mutate({ - mutation, - update: () => { - throw new Error(`Hello... It's me.`); - }, - }) - ) - .then( - () => { - subscriptionHandle.unsubscribe(); - throw new Error("should have thrown"); + await obsQuery.result(); + // we have to actually subscribe to the query to be able to update it + + const handle = client.watchQuery({ query }); + const stream = new ObservableStream(handle); + await stream.takeNext(); + + await expect( + client.mutate({ + mutation, + update: () => { + throw new Error(`Hello... It's me.`); }, - (error) => { - subscriptionHandle.unsubscribe(); - expect(error.message).toBe(`Hello... It's me.`); - } - ); + }) + ).rejects.toThrow( + new ApolloError({ networkError: Error(`Hello... It's me.`) }) + ); }); it("mutate() data should never be `undefined` in case of success", async () => { @@ -1754,16 +1646,13 @@ describe("mutation results", () => { cache: new InMemoryCache({ addTypename: false }), }); - await client - .mutate<{ foo: { bar: string } }>({ - mutation: mutation, - }) - .then((result) => { - // This next line should **not** raise "TS2533: Object is possibly 'null' or 'undefined'.", even without `!` operator - if (!result.data?.foo.bar) { - throw new Error("data was unexpectedly undefined"); - } - }); + const result = await client.mutate<{ foo: { bar: string } }>({ + mutation: mutation, + }); + // This next line should **not** raise "TS2533: Object is possibly 'null' or 'undefined'.", even without `!` operator + if (!result.data?.foo.bar) { + throw new Error("data was unexpectedly undefined"); + } }); it("data might be undefined in case of failure with errorPolicy = ignore", async () => { diff --git a/src/cache/inmemory/__tests__/policies.ts b/src/cache/inmemory/__tests__/policies.ts index 701347d2cc8..af4aefaf8e1 100644 --- a/src/cache/inmemory/__tests__/policies.ts +++ b/src/cache/inmemory/__tests__/policies.ts @@ -13,7 +13,7 @@ import { import { MissingFieldError } from "../.."; import { relayStylePagination, stringifyForDisplay } from "../../../utilities"; import { FieldPolicy, StorageType } from "../policies"; -import { itAsync, MockLink } from "../../../testing/core"; +import { MockLink } from "../../../testing/core"; import { ObservableStream, spyOnConsole } from "../../../testing/internal"; function reverse(s: string) { @@ -3505,188 +3505,186 @@ describe("type policies", function () { }); }); - itAsync( - "can handle Relay-style pagination without args", - (resolve, reject) => { - const cache = new InMemoryCache({ - addTypename: false, - typePolicies: { - Query: { - fields: { - todos: relayStylePagination(), - }, + it("can handle Relay-style pagination without args", async () => { + const cache = new InMemoryCache({ + addTypename: false, + typePolicies: { + Query: { + fields: { + todos: relayStylePagination(), }, }, - }); + }, + }); - const firstQuery = gql` - query TodoQuery { - todos { - totalCount - } + const firstQuery = gql` + query TodoQuery { + todos { + totalCount } - `; + } + `; - const secondQuery = gql` - query TodoQuery { - todos(after: $after, first: $first) { - pageInfo { - __typename - hasNextPage - endCursor - } - totalCount - edges { + const secondQuery = gql` + query TodoQuery { + todos(after: $after, first: $first) { + pageInfo { + __typename + hasNextPage + endCursor + } + totalCount + edges { + __typename + id + node { __typename id - node { - __typename - id - title - } + title } } } - `; + } + `; - const thirdQuery = gql` - query TodoQuery { - todos { - totalCount - extraMetaData - } + const thirdQuery = gql` + query TodoQuery { + todos { + totalCount + extraMetaData } - `; + } + `; - const secondVariables = { - first: 1, - }; + const secondVariables = { + first: 1, + }; - const secondEdges = [ - { - __typename: "TodoEdge", - id: "edge1", - node: { - __typename: "Todo", - id: "1", - title: "Fix the tests", - }, + const secondEdges = [ + { + __typename: "TodoEdge", + id: "edge1", + node: { + __typename: "Todo", + id: "1", + title: "Fix the tests", }, - ]; + }, + ]; - const secondPageInfo = { - __typename: "PageInfo", - endCursor: "YXJyYXljb25uZWN0aW9uOjI=", - hasNextPage: true, - }; + const secondPageInfo = { + __typename: "PageInfo", + endCursor: "YXJyYXljb25uZWN0aW9uOjI=", + hasNextPage: true, + }; - const link = new MockLink([ - { - request: { - query: firstQuery, - }, - result: { - data: { - todos: { - totalCount: 1292, - }, - }, - }, + const link = new MockLink([ + { + request: { + query: firstQuery, }, - { - request: { - query: secondQuery, - variables: secondVariables, - }, - result: { - data: { - todos: { - edges: secondEdges, - pageInfo: secondPageInfo, - totalCount: 1292, - }, + result: { + data: { + todos: { + totalCount: 1292, }, }, }, - { - request: { - query: thirdQuery, - }, - result: { - data: { - todos: { - totalCount: 1293, - extraMetaData: "extra", - }, - }, - }, + }, + { + request: { + query: secondQuery, + variables: secondVariables, }, - ]).setOnError(reject); - - const client = new ApolloClient({ link, cache }); - - client.query({ query: firstQuery }).then((result) => { - expect(result).toEqual({ - loading: false, - networkStatus: NetworkStatus.ready, + result: { data: { todos: { + edges: secondEdges, + pageInfo: secondPageInfo, totalCount: 1292, }, }, - }); - - expect(cache.extract()).toEqual({ - ROOT_QUERY: { - __typename: "Query", + }, + }, + { + request: { + query: thirdQuery, + }, + result: { + data: { todos: { - edges: [], - pageInfo: { - endCursor: "", - hasNextPage: true, - hasPreviousPage: false, - startCursor: "", - }, - totalCount: 1292, + totalCount: 1293, + extraMetaData: "extra", }, }, - }); + }, + }, + ]).setOnError((error) => { + throw new Error(error); + }); - client - .query({ query: secondQuery, variables: secondVariables }) - .then((result) => { - expect(result).toEqual({ - loading: false, - networkStatus: NetworkStatus.ready, - data: { - todos: { - edges: secondEdges, - pageInfo: secondPageInfo, - totalCount: 1292, - }, - }, - }); + const client = new ApolloClient({ link, cache }); - expect(cache.extract()).toMatchSnapshot(); + let result = await client.query({ query: firstQuery }); - client.query({ query: thirdQuery }).then((result) => { - expect(result).toEqual({ - loading: false, - networkStatus: NetworkStatus.ready, - data: { - todos: { - totalCount: 1293, - extraMetaData: "extra", - }, - }, - }); - expect(cache.extract()).toMatchSnapshot(); - resolve(); - }); - }); - }); - } - ); + expect(result).toEqual({ + loading: false, + networkStatus: NetworkStatus.ready, + data: { + todos: { + totalCount: 1292, + }, + }, + }); + + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", + todos: { + edges: [], + pageInfo: { + endCursor: "", + hasNextPage: true, + hasPreviousPage: false, + startCursor: "", + }, + totalCount: 1292, + }, + }, + }); + + result = await client.query({ + query: secondQuery, + variables: secondVariables, + }); + + expect(result).toEqual({ + loading: false, + networkStatus: NetworkStatus.ready, + data: { + todos: { + edges: secondEdges, + pageInfo: secondPageInfo, + totalCount: 1292, + }, + }, + }); + + expect(cache.extract()).toMatchSnapshot(); + + result = await client.query({ query: thirdQuery }); + expect(result).toEqual({ + loading: false, + networkStatus: NetworkStatus.ready, + data: { + todos: { + totalCount: 1293, + extraMetaData: "extra", + }, + }, + }); + expect(cache.extract()).toMatchSnapshot(); + }); it("can handle Relay-style pagination", async () => { const cache = new InMemoryCache({ @@ -4103,7 +4101,7 @@ describe("type policies", function () { }); expect(cache.extract()).toMatchSnapshot(); - observable.fetchMore({ variables: secondVariables }); + await observable.fetchMore({ variables: secondVariables }); { const result = await stream.takeNext(); @@ -4128,7 +4126,7 @@ describe("type policies", function () { expect(cache.extract()).toMatchSnapshot(); } - observable.fetchMore({ variables: thirdVariables }); + await observable.fetchMore({ variables: thirdVariables }); { const result = await stream.takeNext(); @@ -4156,7 +4154,7 @@ describe("type policies", function () { expect(cache.extract()).toMatchSnapshot(); } - observable.fetchMore({ variables: fourthVariables }); + await observable.fetchMore({ variables: fourthVariables }); { const result = await stream.takeNext(); @@ -4187,7 +4185,7 @@ describe("type policies", function () { expect(cache.extract()).toMatchSnapshot(); } - observable.fetchMore({ variables: fifthVariables }); + await observable.fetchMore({ variables: fifthVariables }); { const result = await stream.takeNext(); @@ -5157,7 +5155,7 @@ describe("type policies", function () { expect(personMergeCount).toBe(3); }); - it("can force merging references with non-normalized objects", function () { + it("can force merging references with non-normalized objects", async function () { const nameQuery = gql` query GetName { viewer { @@ -5175,7 +5173,7 @@ describe("type policies", function () { } `; - check( + await check( new InMemoryCache({ typePolicies: { Query: { @@ -5189,7 +5187,7 @@ describe("type policies", function () { }) ); - check( + await check( new InMemoryCache({ typePolicies: { User: { @@ -5199,7 +5197,7 @@ describe("type policies", function () { }) ); - function check(cache: InMemoryCache) { + async function check(cache: InMemoryCache) { // Write nameQuery first, so the existing data will be a // non-normalized object when we write emailQuery next. cache.writeQuery({ @@ -5271,7 +5269,7 @@ describe("type policies", function () { }, }); - cache.reset(); + await cache.reset(); expect(cache.extract()).toEqual({}); // Write emailQuery first, so the existing data will be a diff --git a/src/core/__tests__/ObservableQuery.ts b/src/core/__tests__/ObservableQuery.ts index dfae3dc725c..96ef174df4b 100644 --- a/src/core/__tests__/ObservableQuery.ts +++ b/src/core/__tests__/ObservableQuery.ts @@ -140,7 +140,7 @@ describe("ObservableQuery", () => { expect(data).toEqual(dataOne); } - observable.setOptions({ query, pollInterval: 10 }); + await observable.setOptions({ query, pollInterval: 10 }); { const { data } = await stream.takeNext(); @@ -183,7 +183,7 @@ describe("ObservableQuery", () => { expect(data).toEqual(dataOne); } - observable.setOptions({ query, pollInterval: 0 }); + await observable.setOptions({ query, pollInterval: 0 }); await expect(stream).not.toEmitAnything(); }); @@ -219,7 +219,7 @@ describe("ObservableQuery", () => { expect(data).toEqual(dataOne); } - observable.setOptions({ query, pollInterval: 10 }); + await observable.setOptions({ query, pollInterval: 10 }); { const { data } = await stream.takeNext(); @@ -283,7 +283,7 @@ describe("ObservableQuery", () => { expect(loading).toBe(false); } - observable.refetch(variables2); + await observable.refetch(variables2); { const { loading, networkStatus } = await stream.takeNext(); @@ -351,7 +351,7 @@ describe("ObservableQuery", () => { expect(result.data).toEqual(data); } - observable.refetch(); + await observable.refetch(); { const { loading, networkStatus } = await stream.takeNext(); @@ -462,11 +462,11 @@ describe("ObservableQuery", () => { expect(data).toEqual(dataOne); } - observable.refetch(); + await observable.refetch().catch(() => {}); await stream.takeError(); - observable.refetch(); + await observable.refetch(); await expect(stream).not.toEmitAnything(); }); @@ -492,7 +492,7 @@ describe("ObservableQuery", () => { expect(data).toEqual(dataOne); } - observable.setOptions({ fetchPolicy: "network-only" }); + await observable.setOptions({ fetchPolicy: "network-only" }); { const { data, loading } = await stream.takeNext(); @@ -605,7 +605,7 @@ describe("ObservableQuery", () => { expect(timesFired).toBe(0); } - observable.setOptions({ fetchPolicy: "cache-first" }); + await observable.setOptions({ fetchPolicy: "cache-first" }); { const result = await stream.takeNext(); @@ -779,7 +779,7 @@ describe("ObservableQuery", () => { expect(result.data).toEqual(dataOne); } - observable.setVariables(differentVariables); + await observable.setVariables(differentVariables); { const result = await stream.takeNext(); @@ -929,7 +929,7 @@ describe("ObservableQuery", () => { expect(observable.getCurrentResult().errors).toEqual([error]); } - observable.setVariables(differentVariables); + await observable.setVariables(differentVariables); expect(observable.getCurrentResult().errors).toBeUndefined(); { @@ -981,7 +981,7 @@ describe("ObservableQuery", () => { expect(result.networkStatus).toBe(NetworkStatus.ready); } - observable.setVariables(differentVariables); + await observable.setVariables(differentVariables); { const result = await stream.takeNext(); @@ -1031,7 +1031,7 @@ describe("ObservableQuery", () => { expect(result.networkStatus).toBe(NetworkStatus.ready); } - observable.refetch(differentVariables); + await observable.refetch(differentVariables); { const result = await stream.takeNext(); @@ -1069,7 +1069,7 @@ describe("ObservableQuery", () => { expect(result.data).toEqual(dataOne); - observable.setVariables(variables); + await observable.setVariables(variables); await expect(stream).not.toEmitAnything(); }); @@ -1093,7 +1093,7 @@ describe("ObservableQuery", () => { const stream = new ObservableStream(observable); - observable.setVariables(differentVariables); + await observable.setVariables(differentVariables); const result = await stream.takeNext(); @@ -1139,7 +1139,7 @@ describe("ObservableQuery", () => { }); } - observable.refetch(differentVariables); + await observable.refetch(differentVariables); { const result = await stream.takeNext(); @@ -1188,7 +1188,7 @@ describe("ObservableQuery", () => { }); const stream = new ObservableStream(observableQuery); - observableQuery.refetch({ id: 2 }); + void observableQuery.refetch({ id: 2 }); observers[0].next({ data: dataOne }); observers[0].complete(); @@ -1237,8 +1237,8 @@ describe("ObservableQuery", () => { }); } - observableQuery.refetch({ id: 2 }); - observableQuery.refetch({ id: 3 }); + void observableQuery.refetch({ id: 2 }); + void observableQuery.refetch({ id: 3 }); observers[1].next({ data: dataTwo }); observers[1].complete(); @@ -1290,7 +1290,7 @@ describe("ObservableQuery", () => { const stream = new ObservableStream(observable); await stream.takeNext(); - observable.refetch(differentVariables); + await observable.refetch(differentVariables); const fqbpCalls = mocks.fetchQueryByPolicy.mock.calls; expect(fqbpCalls.length).toBe(2); @@ -1361,7 +1361,7 @@ describe("ObservableQuery", () => { expect(result.data).toEqual(data); expect(result.loading).toBe(false); - observable.refetch(variables2); + await observable.refetch(variables2); } { @@ -1376,7 +1376,7 @@ describe("ObservableQuery", () => { expect(result.data).toEqual(data2); expect(result.loading).toBe(false); - observable.refetch(variables1); + await observable.refetch(variables1); } { @@ -1475,7 +1475,7 @@ describe("ObservableQuery", () => { expect(observable.options.fetchPolicy).toBe("cache-first"); } - observable.refetch(variables2); + await observable.refetch(variables2); { const result = await stream.takeNext(); @@ -1631,12 +1631,9 @@ describe("ObservableQuery", () => { // Make the next network request fail. linkObservable = errorObservable; - try { - await observable.refetch(); - throw new Error("Refetch should have errored"); - } catch (error) { - expect(error).toBe(intentionalNetworkFailure); - } + await expect(() => observable.refetch()).rejects.toThrow( + intentionalNetworkFailure + ); { const result = await stream.takeNext(); @@ -1849,31 +1846,25 @@ describe("ObservableQuery", () => { ); } - await promise.then( - (result) => { - throw new Error( - `unexpected result ${JSON.stringify(result)}; should have thrown` - ); - }, - (error) => { - expect((error as Error).message).toMatch( - "No more mocked responses for the query: query QueryWithVarsVar($vars: [String!])" - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith( - [ - "Called refetch(%o) for query %o, which does not declare a $variables variable.", - "Did you mean to call refetch(variables) instead of refetch({ variables })?", - ].join("\n"), - { variables: { vars: ["d", "e"] } }, - "QueryWithVarsVar" - ); - } + await expect(promise).rejects.toEqual( + expect.objectContaining({ + message: expect.stringMatching( + /No more mocked responses for the query: query QueryWithVarsVar\(\$vars: \[String!\]\)/ + ), + }) + ); + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith( + [ + "Called refetch(%o) for query %o, which does not declare a $variables variable.", + "Did you mean to call refetch(variables) instead of refetch({ variables })?", + ].join("\n"), + { variables: { vars: ["d", "e"] } }, + "QueryWithVarsVar" ); await expect(stream).not.toEmitAnything(); }); - it("should not warn if passed { variables } and query declares $variables", async () => { using _ = spyOnConsole("warn"); @@ -1930,7 +1921,7 @@ describe("ObservableQuery", () => { }); } - observableWithVariablesVar.refetch({ variables: ["d", "e"] }); + await observableWithVariablesVar.refetch({ variables: ["d", "e"] }); { const result = await stream.takeNext(); @@ -2058,7 +2049,7 @@ describe("ObservableQuery", () => { expect(observable.getCurrentResult()).toEqual(result); } - observable.refetch(); + void observable.refetch(); { const result = await stream.takeNext(); @@ -2162,9 +2153,9 @@ describe("ObservableQuery", () => { const observable = queryManager.watchQuery({ query, variables }); - await observable.result().catch((theError: any) => { - expect(theError.graphQLErrors).toEqual([error]); - }); + await expect(observable.result()).rejects.toThrow( + new ApolloError({ graphQLErrors: [error] }) + ); const currentResult = observable.getCurrentResult(); const currentResult2 = observable.getCurrentResult(); @@ -2832,7 +2823,7 @@ describe("ObservableQuery", () => { resultAfterCacheUpdate2 ); - observableQuery.refetch(); + void observableQuery.refetch(); cache.writeQuery({ query, data: cacheValues.update3 }); expect(observableQuery.getCurrentResult()).toStrictEqual( @@ -2912,7 +2903,7 @@ describe("ObservableQuery", () => { expect(observable.getCurrentResult()).toEqual(result); } - queryManager.mutate({ + void queryManager.mutate({ mutation, optimisticResponse, updateQueries, @@ -3113,7 +3104,7 @@ describe("ObservableQuery", () => { expect(observable.query).toBe(result); }); - it("is updated with transformed query when `setOptions` changes the query", () => { + it("is updated with transformed query when `setOptions` changes the query", async () => { const query = gql` query { currentUser { @@ -3146,7 +3137,7 @@ describe("ObservableQuery", () => { } `); - observable.setOptions({ query: updatedQuery }); + await observable.setOptions({ query: updatedQuery }); expect(observable.query).toMatchDocument(gql` query { @@ -3507,9 +3498,9 @@ test("handles changing variables in rapid succession before other request is com }); }); - observable.reobserve({ variables: { department: "HR" } }); + void observable.reobserve({ variables: { department: "HR" } }); await wait(10); - observable.reobserve({ variables: { department: null } }); + void observable.reobserve({ variables: { department: null } }); // Wait for request to finish await wait(50); diff --git a/src/core/__tests__/QueryManager/index.ts b/src/core/__tests__/QueryManager/index.ts index acfffe229d9..5d6d9592bcc 100644 --- a/src/core/__tests__/QueryManager/index.ts +++ b/src/core/__tests__/QueryManager/index.ts @@ -1851,7 +1851,7 @@ describe("QueryManager", () => { await expect(stream).toEmitMatchedValue({ data: data1 }); - queryManager.query({ query: query2 }); + await queryManager.query({ query: query2 }); await expect(stream).toEmitMatchedValue({ data: { @@ -1870,7 +1870,7 @@ describe("QueryManager", () => { itAsync("warns if you forget the template literal tag", async (resolve) => { const queryManager = mockQueryManager(); expect(() => { - queryManager.query({ + void queryManager.query({ // Bamboozle TypeScript into letting us do this query: "string" as any as DocumentNode, }); @@ -5216,7 +5216,7 @@ describe("QueryManager", () => { await expect(stream).toEmitMatchedValue({ data }); - queryManager.mutate({ + await queryManager.mutate({ mutation, variables: mutationVariables, refetchQueries: [{ query, variables }], diff --git a/src/core/__tests__/fetchPolicies.ts b/src/core/__tests__/fetchPolicies.ts index a7316259603..0208b6982c5 100644 --- a/src/core/__tests__/fetchPolicies.ts +++ b/src/core/__tests__/fetchPolicies.ts @@ -482,7 +482,7 @@ describe("no-cache", () => { }); expect(client.cache.extract(true)).toEqual({}); - observable.setVariables({ id: "2" }); + await observable.setVariables({ id: "2" }); await expect(stream).toEmitValue({ loading: true, @@ -497,7 +497,7 @@ describe("no-cache", () => { }); expect(client.cache.extract(true)).toEqual({}); - observable.refetch(); + await observable.refetch(); await expect(stream).toEmitValue({ data: dataWithId(2), @@ -513,7 +513,7 @@ describe("no-cache", () => { }); expect(client.cache.extract(true)).toEqual({}); - observable.refetch({ id: "3" }); + await observable.refetch({ id: "3" }); await expect(stream).toEmitValue({ loading: true, @@ -702,7 +702,7 @@ describe("cache-only", () => { }); expect(observable.options.fetchPolicy).toBe("cache-only"); - observable.refetch(); + await observable.refetch(); await expect(stream).toEmitValue({ loading: false, @@ -768,7 +768,7 @@ describe("cache-and-network", function () { networkStatus: NetworkStatus.ready, }); - observable.setVariables({ id: "2" }); + await observable.setVariables({ id: "2" }); await expect(stream).toEmitValue({ data: {}, @@ -783,7 +783,7 @@ describe("cache-and-network", function () { networkStatus: NetworkStatus.ready, }); - observable.refetch(); + await observable.refetch(); await expect(stream).toEmitValue({ data: dataWithId(2), @@ -797,7 +797,7 @@ describe("cache-and-network", function () { networkStatus: NetworkStatus.ready, }); - observable.refetch({ id: "3" }); + await observable.refetch({ id: "3" }); await expect(stream).toEmitValue({ data: {},