Skip to content

Commit 12f363b

Browse files
sijadtrojanowski
authored andcommitted
feat: implement useSubscription (#37)
1 parent 6c9c90f commit 12f363b

File tree

3 files changed

+423
-0
lines changed

3 files changed

+423
-0
lines changed
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
import { ApolloClient } from 'apollo-client';
2+
import { Operation } from 'apollo-link';
3+
import { MockSubscriptionLink } from 'apollo-link-mock';
4+
import gql from 'graphql-tag';
5+
import React from 'react';
6+
import { cleanup, render } from 'react-testing-library';
7+
8+
import { ApolloProvider, useSubscription } from '..';
9+
import createClient from '../__testutils__/createClient';
10+
import { SAMPLE_TASKS } from '../__testutils__/data';
11+
import wait from '../__testutils__/wait';
12+
13+
jest.mock('../internal/actHack');
14+
15+
const TASKS_SUBSCRIPTION = gql`
16+
subscription NewTasks {
17+
task {
18+
id
19+
text
20+
completed
21+
}
22+
}
23+
`;
24+
25+
const FILTERED_TASKS_SUBSCRIPTION = gql`
26+
subscription FilteredNewTasks($completed: Boolean!) {
27+
task(completed: $completed) {
28+
id
29+
text
30+
completed
31+
}
32+
}
33+
`;
34+
35+
const results = SAMPLE_TASKS.map(task => ({
36+
result: { data: { task } },
37+
}));
38+
39+
const complitedResults = SAMPLE_TASKS.filter(task => task.completed).map(
40+
task => ({
41+
result: { data: { task } },
42+
})
43+
);
44+
45+
const uncomplitedResults = SAMPLE_TASKS.filter(task => !task.completed).map(
46+
task => ({
47+
result: { data: { task } },
48+
})
49+
);
50+
51+
afterEach(cleanup);
52+
53+
it('should return the subscription data', async () => {
54+
const link = new MockSubscriptionLink();
55+
const client = createClient({ link });
56+
57+
let count = 0;
58+
59+
const Component = () => {
60+
const { data, loading, error } = useSubscription(TASKS_SUBSCRIPTION);
61+
if (count === 0) {
62+
expect(loading).toBe(true);
63+
expect(error).toBeUndefined();
64+
expect(data).toBeUndefined();
65+
} else if (count === 1) {
66+
expect(loading).toBe(false);
67+
expect(error).toBeUndefined();
68+
expect(data).toEqual(results[0].result.data);
69+
} else if (count === 2) {
70+
expect(loading).toBe(false);
71+
expect(error).toBeUndefined();
72+
expect(data).toEqual(results[1].result.data);
73+
} else if (count === 3) {
74+
expect(loading).toBe(false);
75+
expect(error).toBeUndefined();
76+
expect(data).toEqual(results[2].result.data);
77+
}
78+
count++;
79+
return null;
80+
};
81+
82+
render(
83+
<ApolloProvider client={client}>
84+
<Component />
85+
</ApolloProvider>
86+
);
87+
88+
for (let i = 0; i < 3; i++) {
89+
link.simulateResult(results[i]);
90+
await wait();
91+
}
92+
93+
expect(count).toBe(4);
94+
});
95+
96+
it('should return the subscription error', async () => {
97+
const link = new MockSubscriptionLink();
98+
const client = createClient({ link });
99+
100+
const subscriptionError = {
101+
error: new Error('error occurred'),
102+
};
103+
104+
let count = 0;
105+
106+
const Component = () => {
107+
const { data, loading, error } = useSubscription(TASKS_SUBSCRIPTION);
108+
if (count === 0) {
109+
expect(loading).toBe(true);
110+
expect(error).toBeUndefined();
111+
expect(data).toBeUndefined();
112+
} else if (count === 1) {
113+
expect(loading).toBe(false);
114+
expect(error).toEqual(new Error('error occurred'));
115+
expect(data).toBeUndefined();
116+
}
117+
count++;
118+
return null;
119+
};
120+
121+
render(
122+
<ApolloProvider client={client}>
123+
<Component />
124+
</ApolloProvider>
125+
);
126+
127+
link.simulateResult(subscriptionError);
128+
await wait();
129+
130+
expect(count).toBe(2);
131+
});
132+
133+
it('should call provided onSubscriptionData', async () => {
134+
const link = new MockSubscriptionLink();
135+
const client = createClient({ link });
136+
137+
let count = 0;
138+
139+
const Component = () => {
140+
useSubscription(TASKS_SUBSCRIPTION, {
141+
onSubscriptionData: opts => {
142+
const { loading, data, error } = opts.subscriptionData;
143+
expect(opts.client).toBeInstanceOf(ApolloClient);
144+
expect(data).toEqual(results[count].result.data);
145+
expect(loading).toBe(false);
146+
expect(error).toBeUndefined();
147+
count++;
148+
},
149+
});
150+
return null;
151+
};
152+
153+
render(
154+
<ApolloProvider client={client}>
155+
<Component />
156+
</ApolloProvider>
157+
);
158+
159+
for (let i = 0; i < 3; i++) {
160+
link.simulateResult(results[i]);
161+
await wait();
162+
}
163+
164+
expect(count).toBe(3);
165+
});
166+
167+
it('should execute subscription with provided variables', async () => {
168+
const variables = { completed: true };
169+
170+
class MockSubscriptionLinkOverride extends MockSubscriptionLink {
171+
request(req: Operation) {
172+
expect(req.variables).toEqual(variables);
173+
return super.request(req);
174+
}
175+
}
176+
177+
const link = new MockSubscriptionLinkOverride();
178+
179+
const client = createClient({ link });
180+
181+
let count = 0;
182+
183+
const Component = () => {
184+
const { data, loading, error } = useSubscription(
185+
FILTERED_TASKS_SUBSCRIPTION,
186+
{ variables }
187+
);
188+
if (count === 0) {
189+
expect(loading).toBe(true);
190+
expect(error).toBeUndefined();
191+
expect(data).toBeUndefined();
192+
} else if (count === 1) {
193+
expect(loading).toBe(false);
194+
expect(error).toBeUndefined();
195+
expect(data).toEqual(complitedResults[0].result.data);
196+
}
197+
count++;
198+
return null;
199+
};
200+
201+
render(
202+
<ApolloProvider client={client}>
203+
<Component />
204+
</ApolloProvider>
205+
);
206+
207+
link.simulateResult(complitedResults[0]);
208+
await wait();
209+
210+
expect(count).toBe(2);
211+
});
212+
213+
it('should not re-subscription if variables have not changed', async () => {
214+
const variables = { completed: true };
215+
216+
class MockSubscriptionLinkOverride extends MockSubscriptionLink {
217+
request(req: Operation) {
218+
expect(req.variables).toEqual(variables);
219+
return super.request(req);
220+
}
221+
}
222+
223+
const link = new MockSubscriptionLinkOverride();
224+
225+
const client = createClient({ link });
226+
227+
let count = 0;
228+
229+
const Component = () => {
230+
const [, forceRender] = React.useState(0);
231+
const { data, loading, error } = useSubscription(
232+
FILTERED_TASKS_SUBSCRIPTION,
233+
{ variables }
234+
);
235+
if (count === 0) {
236+
expect(loading).toBe(true);
237+
expect(error).toBeUndefined();
238+
expect(data).toBeUndefined();
239+
} else if (count === 1) {
240+
expect(loading).toBe(false);
241+
expect(error).toBeUndefined();
242+
expect(data).toEqual(complitedResults[0].result.data);
243+
forceRender(c => c + 1);
244+
} else if (count === 2) {
245+
expect(loading).toBe(false);
246+
}
247+
count++;
248+
return null;
249+
};
250+
251+
render(
252+
<ApolloProvider client={client}>
253+
<Component />
254+
</ApolloProvider>
255+
);
256+
257+
link.simulateResult(complitedResults[0]);
258+
await wait();
259+
260+
expect(count).toBe(3);
261+
});
262+
263+
it('should re-subscription if variables have changed', async () => {
264+
class MockSubscriptionLinkOverride extends MockSubscriptionLink {
265+
variables: any;
266+
request(req: Operation) {
267+
this.variables = req.variables;
268+
return super.request(req);
269+
}
270+
271+
simulateResult() {
272+
if (this.variables.completed) {
273+
return super.simulateResult(complitedResults[0]);
274+
} else {
275+
return super.simulateResult(uncomplitedResults[0]);
276+
}
277+
}
278+
}
279+
280+
const link = new MockSubscriptionLinkOverride();
281+
282+
const client = createClient({ link });
283+
284+
let count = 0;
285+
286+
const Component = () => {
287+
const [completed, setCompleted] = React.useState(false);
288+
const { data, loading, error } = useSubscription(
289+
FILTERED_TASKS_SUBSCRIPTION,
290+
{ variables: { completed } }
291+
);
292+
if (count === 0) {
293+
expect(loading).toBe(true);
294+
expect(error).toBeUndefined();
295+
expect(data).toBeUndefined();
296+
} else if (count === 1) {
297+
expect(loading).toBe(false);
298+
expect(error).toBeUndefined();
299+
expect(data).toEqual(uncomplitedResults[0].result.data);
300+
setCompleted(true);
301+
} else if (count === 2) {
302+
// TODO fix this
303+
expect(loading).toBe(false);
304+
} else if (count === 3) {
305+
expect(loading).toBe(true);
306+
expect(error).toBeUndefined();
307+
expect(data).toBeUndefined();
308+
} else if (count === 4) {
309+
expect(loading).toBe(false);
310+
expect(error).toBeUndefined();
311+
expect(data).toEqual(complitedResults[0].result.data);
312+
}
313+
count++;
314+
return null;
315+
};
316+
317+
render(
318+
<ApolloProvider client={client}>
319+
<Component />
320+
</ApolloProvider>
321+
);
322+
323+
link.simulateResult();
324+
await wait();
325+
link.simulateResult();
326+
await wait();
327+
328+
expect(count).toBe(5);
329+
});

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './useMutation';
33
export * from './ApolloContext';
44
export * from './SuspenseSSR';
55
export * from './getMarkupFromTree';
6+
export * from './useSubscription';

0 commit comments

Comments
 (0)