Skip to content

Commit 2f9a815

Browse files
committed
Let types flow naturally from ObservableQuery
Tests are changed because the `ObservableQuery` methods must be mocked or spied on before constructing a `QueryRef`. Because the `QueryRef` will immediately bind the `ObservableQuery` methods internally. Fixes #2346
1 parent 2d1e27d commit 2f9a815

File tree

4 files changed

+110
-97
lines changed

4 files changed

+110
-97
lines changed

.changeset/thin-otters-move.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'apollo-angular': patch
3+
---
4+
5+
Let types flow naturally from `ObservableQuery`

packages/apollo-angular/src/query-ref.ts

+65-86
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {
44
ApolloQueryResult,
55
ObservableQuery,
66
OperationVariables,
7-
SubscribeToMoreOptions,
87
TypedDocumentNode,
98
} from '@apollo/client/core';
109
import { NetworkStatus } from '@apollo/client/core';
@@ -43,99 +42,79 @@ function useInitialLoading<T, V extends OperationVariables>(obsQuery: Observable
4342
export type QueryRefFromDocument<T extends TypedDocumentNode> =
4443
T extends TypedDocumentNode<infer R, infer V> ? QueryRef<R, V & OperationVariables> : never;
4544

46-
export class QueryRef<TData, TVariables extends OperationVariables = EmptyObject> {
45+
export class QueryRef<TData, TVariables extends OperationVariables = EmptyObject>
46+
implements
47+
Pick<
48+
ObservableQuery<TData, TVariables>,
49+
| 'queryId'
50+
| 'options'
51+
| 'variables'
52+
| 'result'
53+
| 'getCurrentResult'
54+
| 'getLastResult'
55+
| 'getLastError'
56+
| 'resetLastResults'
57+
| 'refetch'
58+
| 'fetchMore'
59+
| 'subscribeToMore'
60+
| 'updateQuery'
61+
| 'stopPolling'
62+
| 'startPolling'
63+
| 'setOptions'
64+
| 'setVariables'
65+
>
66+
{
4767
public readonly valueChanges: Observable<ApolloQueryResult<TData>>;
48-
public readonly queryId: ObservableQuery<TData, TVariables>['queryId'];
68+
69+
// Types flow straight from ObservableQuery
70+
public readonly queryId;
71+
public readonly result;
72+
public readonly getCurrentResult;
73+
public readonly getLastResult;
74+
public readonly getLastError;
75+
public readonly resetLastResults;
76+
public readonly refetch;
77+
public readonly fetchMore;
78+
public readonly subscribeToMore;
79+
public readonly updateQuery;
80+
public readonly stopPolling;
81+
public readonly startPolling;
82+
public readonly setOptions;
83+
public readonly setVariables;
4984

5085
constructor(
51-
private readonly obsQuery: ObservableQuery<TData, TVariables>,
86+
private readonly query: ObservableQuery<TData, TVariables>,
5287
ngZone: NgZone,
5388
options: WatchQueryOptions<TVariables, TData>,
5489
) {
55-
const wrapped = wrapWithZone(from(fixObservable(this.obsQuery)), ngZone);
90+
const wrapped = wrapWithZone(from(fixObservable(this.query)), ngZone);
5691

5792
this.valueChanges = options.useInitialLoading
58-
? wrapped.pipe(useInitialLoading(this.obsQuery))
93+
? wrapped.pipe(useInitialLoading(this.query))
5994
: wrapped;
60-
this.queryId = this.obsQuery.queryId;
61-
}
62-
63-
// ObservableQuery's methods
64-
65-
public get options(): ObservableQuery<TData, TVariables>['options'] {
66-
return this.obsQuery.options;
67-
}
68-
69-
public get variables(): ObservableQuery<TData, TVariables>['variables'] {
70-
return this.obsQuery.variables;
71-
}
72-
73-
public result(): ReturnType<ObservableQuery<TData, TVariables>['result']> {
74-
return this.obsQuery.result();
75-
}
76-
77-
public getCurrentResult(): ReturnType<ObservableQuery<TData, TVariables>['getCurrentResult']> {
78-
return this.obsQuery.getCurrentResult();
79-
}
80-
81-
public getLastResult(): ReturnType<ObservableQuery<TData, TVariables>['getLastResult']> {
82-
return this.obsQuery.getLastResult();
83-
}
84-
85-
public getLastError(): ReturnType<ObservableQuery<TData, TVariables>['getLastError']> {
86-
return this.obsQuery.getLastError();
87-
}
88-
89-
public resetLastResults(): ReturnType<ObservableQuery<TData, TVariables>['resetLastResults']> {
90-
return this.obsQuery.resetLastResults();
91-
}
92-
93-
public refetch(
94-
variables?: Parameters<ObservableQuery<TData, TVariables>['refetch']>[0],
95-
): ReturnType<ObservableQuery<TData, TVariables>['refetch']> {
96-
return this.obsQuery.refetch(variables);
97-
}
98-
99-
public fetchMore<TFetchVars extends OperationVariables = TVariables>(
100-
fetchMoreOptions: Parameters<ObservableQuery<TData, TFetchVars>['fetchMore']>[0],
101-
): ReturnType<ObservableQuery<TData, TFetchVars>['fetchMore']> {
102-
return this.obsQuery.fetchMore(fetchMoreOptions);
103-
}
104-
105-
public subscribeToMore<
106-
TSubscriptionData = TData,
107-
TSubscriptionVariables extends OperationVariables = TVariables,
108-
>(
109-
options: SubscribeToMoreOptions<TData, TSubscriptionVariables, TSubscriptionData, TVariables>,
110-
): ReturnType<ObservableQuery<TData, TVariables>['subscribeToMore']> {
111-
return this.obsQuery.subscribeToMore(options);
112-
}
113-
114-
public updateQuery(
115-
mapFn: Parameters<ObservableQuery<TData, TVariables>['updateQuery']>[0],
116-
): ReturnType<ObservableQuery<TData, TVariables>['updateQuery']> {
117-
return this.obsQuery.updateQuery(mapFn);
118-
}
119-
120-
public stopPolling(): ReturnType<ObservableQuery<TData, TVariables>['stopPolling']> {
121-
return this.obsQuery.stopPolling();
122-
}
123-
124-
public startPolling(
125-
pollInterval: Parameters<ObservableQuery<TData, TVariables>['startPolling']>[0],
126-
): ReturnType<ObservableQuery<TData, TVariables>['startPolling']> {
127-
return this.obsQuery.startPolling(pollInterval);
128-
}
129-
130-
public setOptions(
131-
opts: Parameters<ObservableQuery<TData, TVariables>['setOptions']>[0],
132-
): ReturnType<ObservableQuery<TData, TVariables>['setOptions']> {
133-
return this.obsQuery.setOptions(opts);
134-
}
135-
136-
public setVariables(
137-
variables: Parameters<ObservableQuery<TData, TVariables>['setVariables']>[0],
138-
): ReturnType<ObservableQuery<TData, TVariables>['setVariables']> {
139-
return this.obsQuery.setVariables(variables);
95+
this.queryId = this.query.queryId;
96+
97+
// ObservableQuery's methods
98+
this.result = this.query.result.bind(this.query);
99+
this.getCurrentResult = this.query.getCurrentResult.bind(this.query);
100+
this.getLastResult = this.query.getLastResult.bind(this.query);
101+
this.getLastError = this.query.getLastError.bind(this.query);
102+
this.resetLastResults = this.query.resetLastResults.bind(this.query);
103+
this.refetch = this.query.refetch.bind(this.query);
104+
this.fetchMore = this.query.fetchMore.bind(this.query);
105+
this.subscribeToMore = this.query.subscribeToMore.bind(this.query);
106+
this.updateQuery = this.query.updateQuery.bind(this.query);
107+
this.stopPolling = this.query.stopPolling.bind(this.query);
108+
this.startPolling = this.query.startPolling.bind(this.query);
109+
this.setOptions = this.query.setOptions.bind(this.query);
110+
this.setVariables = this.query.setVariables.bind(this.query);
111+
}
112+
113+
public get options() {
114+
return this.query.options;
115+
}
116+
117+
public get variables() {
118+
return this.query.variables;
140119
}
141120
}

packages/apollo-angular/tests/Apollo.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ describe('Apollo', () => {
8383
`,
8484
};
8585

86-
client.watchQuery = jest.fn().mockReturnValue(new Observable());
86+
const spy = jest.spyOn(client, 'watchQuery');
8787
apollo.watchQuery(options);
8888

89-
expect(client.watchQuery).toBeCalledWith(options);
89+
expect(spy).toBeCalledWith(options);
9090
});
9191

9292
test('should be able to refetch', (done: jest.DoneCallback) => {

packages/apollo-angular/tests/QueryRef.spec.ts

+38-9
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ const createClient = (link: ApolloLink) =>
1212
cache: new InMemoryCache(),
1313
});
1414

15+
type Result = {
16+
heroes: { name: string }[];
17+
};
18+
19+
type Variables = {
20+
foo?: number;
21+
};
22+
1523
const heroesOperation = {
16-
query: gql`
24+
query: gql<Result, Variables>`
1725
query allHeroes {
1826
heroes {
1927
name
@@ -40,8 +48,7 @@ const Batman = {
4048
describe('QueryRef', () => {
4149
let ngZone: NgZone;
4250
let client: ApolloClient<any>;
43-
let obsQuery: ObservableQuery<any>;
44-
let queryRef: QueryRef<any>;
51+
let obsQuery: ObservableQuery<Result, Variables>;
4552

4653
beforeEach(() => {
4754
ngZone = { run: jest.fn(cb => cb()) } as any;
@@ -58,10 +65,14 @@ describe('QueryRef', () => {
5865

5966
client = createClient(mockedLink);
6067
obsQuery = client.watchQuery(heroesOperation);
61-
queryRef = new QueryRef<any>(obsQuery, ngZone, {} as any);
6268
});
6369

70+
function createQueryRef(obsQuery: ObservableQuery<Result>): QueryRef<Result, Variables> {
71+
return new QueryRef(obsQuery, ngZone, { query: heroesOperation.query });
72+
}
73+
6474
test('should listen to changes', done => {
75+
const queryRef = createQueryRef(obsQuery);
6576
queryRef.valueChanges.subscribe({
6677
next: result => {
6778
expect(result.data).toBeDefined();
@@ -77,6 +88,7 @@ describe('QueryRef', () => {
7788
const mockCallback = jest.fn();
7889
obsQuery.refetch = mockCallback;
7990

91+
const queryRef = createQueryRef(obsQuery);
8092
queryRef.refetch();
8193

8294
expect(mockCallback.mock.calls.length).toBe(1);
@@ -85,6 +97,7 @@ describe('QueryRef', () => {
8597
test('should be able refetch and receive new results', done => {
8698
let calls = 0;
8799

100+
const queryRef = createQueryRef(obsQuery);
88101
queryRef.valueChanges.subscribe({
89102
next: result => {
90103
calls++;
@@ -110,6 +123,7 @@ describe('QueryRef', () => {
110123

111124
test('should be able refetch and receive new results after using rxjs operator', done => {
112125
let calls = 0;
126+
const queryRef = createQueryRef(obsQuery);
113127
const obs = queryRef.valueChanges;
114128

115129
obs.pipe(map(result => result.data)).subscribe({
@@ -139,9 +153,10 @@ describe('QueryRef', () => {
139153

140154
test('should be able to call updateQuery()', () => {
141155
const mockCallback = jest.fn();
142-
const mapFn = () => ({});
156+
const mapFn = () => undefined;
143157
obsQuery.updateQuery = mockCallback;
144158

159+
const queryRef = createQueryRef(obsQuery);
145160
queryRef.updateQuery(mapFn);
146161

147162
expect(mockCallback.mock.calls.length).toBe(1);
@@ -152,6 +167,7 @@ describe('QueryRef', () => {
152167
const mockCallback = jest.fn();
153168
obsQuery.result = mockCallback.mockReturnValue('expected');
154169

170+
const queryRef = createQueryRef(obsQuery);
155171
const result = queryRef.result();
156172

157173
expect(result).toBe('expected');
@@ -160,6 +176,7 @@ describe('QueryRef', () => {
160176

161177
test('should be able to call getCurrentResult() and get updated results', done => {
162178
let calls = 0;
179+
const queryRef = createQueryRef(obsQuery);
163180
const obs = queryRef.valueChanges;
164181

165182
obs.pipe(map(result => result.data)).subscribe({
@@ -189,6 +206,7 @@ describe('QueryRef', () => {
189206
const mockCallback = jest.fn();
190207
obsQuery.getLastResult = mockCallback.mockReturnValue('expected');
191208

209+
const queryRef = createQueryRef(obsQuery);
192210
const result = queryRef.getLastResult();
193211

194212
expect(result).toBe('expected');
@@ -199,6 +217,7 @@ describe('QueryRef', () => {
199217
const mockCallback = jest.fn();
200218
obsQuery.getLastError = mockCallback.mockReturnValue('expected');
201219

220+
const queryRef = createQueryRef(obsQuery);
202221
const result = queryRef.getLastError();
203222

204223
expect(result).toBe('expected');
@@ -209,6 +228,7 @@ describe('QueryRef', () => {
209228
const mockCallback = jest.fn();
210229
obsQuery.resetLastResults = mockCallback.mockReturnValue('expected');
211230

231+
const queryRef = createQueryRef(obsQuery);
212232
const result = queryRef.resetLastResults();
213233

214234
expect(result).toBe('expected');
@@ -217,10 +237,11 @@ describe('QueryRef', () => {
217237

218238
test('should be able to call fetchMore()', () => {
219239
const mockCallback = jest.fn();
220-
const opts = { foo: 1 };
240+
const opts = { variables: { foo: 1 } };
221241
obsQuery.fetchMore = mockCallback.mockReturnValue('expected');
222242

223-
const result = queryRef.fetchMore(opts as any);
243+
const queryRef = createQueryRef(obsQuery);
244+
const result = queryRef.fetchMore(opts);
224245

225246
expect(result).toBe('expected');
226247
expect(mockCallback.mock.calls.length).toBe(1);
@@ -229,10 +250,11 @@ describe('QueryRef', () => {
229250

230251
test('should be able to call subscribeToMore()', () => {
231252
const mockCallback = jest.fn();
232-
const opts = { foo: 1 };
253+
const opts = { document: heroesOperation.query };
233254
obsQuery.subscribeToMore = mockCallback;
234255

235-
queryRef.subscribeToMore(opts as any);
256+
const queryRef = createQueryRef(obsQuery);
257+
queryRef.subscribeToMore(opts);
236258

237259
expect(mockCallback.mock.calls.length).toBe(1);
238260
expect(mockCallback.mock.calls[0][0]).toBe(opts);
@@ -242,6 +264,7 @@ describe('QueryRef', () => {
242264
const mockCallback = jest.fn();
243265
obsQuery.stopPolling = mockCallback;
244266

267+
const queryRef = createQueryRef(obsQuery);
245268
queryRef.stopPolling();
246269

247270
expect(mockCallback.mock.calls.length).toBe(1);
@@ -251,6 +274,7 @@ describe('QueryRef', () => {
251274
const mockCallback = jest.fn();
252275
obsQuery.startPolling = mockCallback;
253276

277+
const queryRef = createQueryRef(obsQuery);
254278
queryRef.startPolling(3000);
255279

256280
expect(mockCallback.mock.calls.length).toBe(1);
@@ -262,6 +286,7 @@ describe('QueryRef', () => {
262286
const opts = {};
263287
obsQuery.setOptions = mockCallback.mockReturnValue('expected');
264288

289+
const queryRef = createQueryRef(obsQuery);
265290
const result = queryRef.setOptions(opts);
266291

267292
expect(result).toBe('expected');
@@ -274,6 +299,7 @@ describe('QueryRef', () => {
274299
const variables = {};
275300
obsQuery.setVariables = mockCallback.mockReturnValue('expected');
276301

302+
const queryRef = createQueryRef(obsQuery);
277303
const result = queryRef.setVariables(variables);
278304

279305
expect(result).toBe('expected');
@@ -282,6 +308,7 @@ describe('QueryRef', () => {
282308
});
283309

284310
test('should handle multiple subscribers', done => {
311+
const queryRef = createQueryRef(obsQuery);
285312
const obsFirst = queryRef.valueChanges;
286313
const obsSecond = queryRef.valueChanges;
287314

@@ -338,6 +365,7 @@ describe('QueryRef', () => {
338365
});
339366

340367
test('should unsubscribe', done => {
368+
const queryRef = createQueryRef(obsQuery);
341369
const obs = queryRef.valueChanges;
342370
const id = queryRef.queryId;
343371

@@ -356,6 +384,7 @@ describe('QueryRef', () => {
356384

357385
test('should unsubscribe based on rxjs operators', done => {
358386
const gate = new Subject<void>();
387+
const queryRef = createQueryRef(obsQuery);
359388
const obs = queryRef.valueChanges.pipe(takeUntil(gate));
360389
const id = queryRef.queryId;
361390

0 commit comments

Comments
 (0)