Skip to content

Commit

Permalink
Merge pull request #7813 from dannycochran/getObservableQueries
Browse files Browse the repository at this point in the history
Provide client.getObservableQueries method for retrieving currently active
ObservableQuery objects.
  • Loading branch information
benjamn authored Jun 4, 2021
2 parents 91c9a1f + 4a8e265 commit ff80d89
Show file tree
Hide file tree
Showing 8 changed files with 472 additions and 142 deletions.
12 changes: 12 additions & 0 deletions src/__tests__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2442,6 +2442,18 @@ describe('client', () => {
expect(spy).toHaveBeenCalledWith(options);
});

it('has a getObservableQueries method which calls QueryManager', async () => {
const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
});

// @ts-ignore
const spy = jest.spyOn(client.queryManager, 'getObservableQueries');
await client.getObservableQueries();
expect(spy).toHaveBeenCalled();
});

itAsync('should propagate errors from network interface to observers', (resolve, reject) => {
const link = ApolloLink.from([
() =>
Expand Down
272 changes: 270 additions & 2 deletions src/__tests__/refetchQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ describe("client.refetchQueries", () => {
operation.operationName.split("").forEach(letter => {
data[letter.toLowerCase()] = letter.toUpperCase();
});
observer.next({ data });
observer.complete();
function finish() {
observer.next({ data });
observer.complete();
}
if (typeof operation.variables.delay === "number") {
setTimeout(finish, operation.variables.delay);
} else finish();
})),
});
}
Expand Down Expand Up @@ -351,6 +356,269 @@ describe("client.refetchQueries", () => {
resolve();
});

itAsync('includes all queries when options.include === "all"', async (resolve, reject) => {
const client = makeClient();
const [
aObs,
bObs,
abObs,
] = await setup(client);

const ayyResults = await client.refetchQueries({
include: "all",

updateCache(cache) {
cache.writeQuery({
query: aQuery,
data: {
a: "Ayy",
},
});
},

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "Ayy" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "Ayy", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(ayyResults);

expect(ayyResults).toEqual([
{ a: "Ayy" },
{ a: "Ayy", b: "B" },
{ b: "B" },
]);

const beeResults = await client.refetchQueries({
include: "all",

updateCache(cache) {
cache.writeQuery({
query: bQuery,
data: {
b: "Bee",
},
});
},

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "Ayy" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "Bee" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "Ayy", b: "Bee" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return diff.result;
},
});

sortObjects(beeResults);

expect(beeResults).toEqual([
{ a: "Ayy" },
{ a: "Ayy", b: "Bee" },
{ b: "Bee" },
]);

unsubscribe();
resolve();
});

itAsync('includes all active queries when options.include === "active"', async (resolve, reject) => {
const client = makeClient();
const [
aObs,
bObs,
abObs,
] = await setup(client);

const extraObs = client.watchQuery({ query: abQuery });
expect(extraObs.hasObservers()).toBe(false);

const activeResults = await client.refetchQueries({
include: "active",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(activeResults);

expect(activeResults).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
{ b: "B" },
]);

subs.push(extraObs.subscribe({
next(result) {
expect(result).toEqual({ a: "A", b: "B" });
},
}));
expect(extraObs.hasObservers()).toBe(true);

const resultsAfterSubscribe = await client.refetchQueries({
include: "active",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else if (obs === extraObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(resultsAfterSubscribe);

expect(resultsAfterSubscribe).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
// Included thanks to extraObs this time.
{ a: "A", b: "B" },
// Sorted last by sortObjects.
{ b: "B" },
]);

unsubscribe();
resolve();
});

itAsync('should not include unwatched single queries', async (resolve, reject) => {
const client = makeClient();
const [
aObs,
bObs,
abObs,
] = await setup(client);

const delayedQuery = gql`query DELAYED { d e l a y e d }`;

client.query({
query: delayedQuery,
variables: {
// Delay this query by 10 seconds so it stays in-flight.
delay: 10000,
},
}).catch(reject);

const queries = client["queryManager"]["queries"];
expect(queries.size).toBe(4);

queries.forEach((queryInfo, queryId) => {
if (
queryId === "1" ||
queryId === "2" ||
queryId === "3"
) {
expect(queryInfo.observableQuery).toBeInstanceOf(ObservableQuery);
} else if (queryId === "4") {
// One-off client.query-style queries never get an ObservableQuery, so
// they should not be included by include: "active".
expect(queryInfo.observableQuery).toBe(null);
expect(queryInfo.document).toBe(delayedQuery);
}
});

const activeResults = await client.refetchQueries({
include: "active",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(activeResults);

expect(activeResults).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
{ b: "B" },
]);

const allResults = await client.refetchQueries({
include: "all",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(allResults);

expect(allResults).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
{ b: "B" },
]);

unsubscribe();
client.stop();

expect(queries.size).toBe(0);

resolve();
});

itAsync("refetches watched queries if onQueryUpdated not provided", async (resolve, reject) => {
const client = makeClient();
const [
Expand Down
15 changes: 15 additions & 0 deletions src/core/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RefetchQueriesOptions,
RefetchQueriesResult,
InternalRefetchQueriesResult,
RefetchQueriesInclude,
} from './types';

import {
Expand Down Expand Up @@ -564,6 +565,20 @@ export class ApolloClient<TCacheShape> implements DataProxy {
return result;
}

/**
* Get all currently active `ObservableQuery` objects, in a `Map` keyed by
* query ID strings. An "active" query is one that has observers and a
* `fetchPolicy` other than "standby" or "cache-only". You can include all
* `ObservableQuery` objects (including the inactive ones) by passing "all"
* instead of "active", or you can include just a subset of active queries by
* passing an array of query names or DocumentNode objects.
*/
public getObservableQueries(
include: RefetchQueriesInclude = "active",
): Map<string, ObservableQuery<any>> {
return this.queryManager.getObservableQueries(include);
}

/**
* Exposes the cache's complete state, in a serializable format for later restoration.
*/
Expand Down
Loading

0 comments on commit ff80d89

Please sign in to comment.