Skip to content

Commit 2da1800

Browse files
committed
feat(subscriptions): add support for subscriptions.
closes #79
1 parent e12f262 commit 2da1800

File tree

10 files changed

+249
-2
lines changed

10 files changed

+249
-2
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
type Subscription {
2+
# Like story subscription
3+
LikeStory(input: LikeStorySubscriptionInput): LikeStorySubscriptionPayload
4+
}
5+
6+
input LikeStorySubscriptionInput {
7+
clientSubscriptionId: String
8+
id: ID!
9+
}
10+
11+
type LikeStorySubscriptionPayload {
12+
clientSubscriptionId: String
13+
doesViewerLike: Boolean
14+
}

src/__test-data__/utils.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,27 @@ export function getDefLocations() {
6868
path: path.resolve('src/__test-data__/schema/mutation.gql'),
6969
},
7070

71+
Subscription: {
72+
start: { line: 1, column: 1 },
73+
end: { line: 4, column: 2 },
74+
path: path.resolve('src/__test-data__/schema/subscription.gql'),
75+
},
76+
Subscription_LikeStory: { // eslint-disable-line camelcase
77+
start: { line: 3, column: 3 },
78+
end: { line: 3, column: 77 },
79+
path: path.resolve('src/__test-data__/schema/subscription.gql'),
80+
},
81+
Subscription_LikeStorySubscriptionInput: { // eslint-disable-line camelcase
82+
start: { line: 6, column: 1 },
83+
end: { line: 9, column: 2 },
84+
path: path.resolve('src/__test-data__/schema/subscription.gql'),
85+
},
86+
Subscription_LikeStorySubscriptionInput_id: { // eslint-disable-line camelcase
87+
start: { line: 8, column: 3 },
88+
end: { line: 8, column: 10 },
89+
path: path.resolve('src/__test-data__/schema/subscription.gql'),
90+
},
91+
7192
Query: {
7293
start: { line: 2, column: 1 },
7394
end: { line: 6, column: 2 },
@@ -213,6 +234,16 @@ export function getHints() {
213234
text: 'NewPlayer',
214235
type: 'Object',
215236
},
237+
{
238+
description: '',
239+
text: 'Subscription',
240+
type: 'Object',
241+
},
242+
{
243+
description: '',
244+
text: 'LikeStorySubscriptionPayload',
245+
type: 'Object',
246+
},
216247
],
217248

218249
OutputTypes: [
@@ -276,6 +307,16 @@ export function getHints() {
276307
text: 'CustomScalar',
277308
type: 'Scalar',
278309
},
310+
{
311+
description: '',
312+
text: 'Subscription',
313+
type: 'Object',
314+
},
315+
{
316+
description: '',
317+
text: 'LikeStorySubscriptionPayload',
318+
type: 'Object',
319+
},
279320
{
280321
description: 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.',
281322
text: 'String',
@@ -354,6 +395,16 @@ export function getHints() {
354395
text: 'Entity',
355396
type: 'Union',
356397
},
398+
{
399+
description: '',
400+
text: 'Subscription',
401+
type: 'Object',
402+
},
403+
{
404+
description: '',
405+
text: 'LikeStorySubscriptionPayload',
406+
type: 'Object',
407+
},
357408
],
358409

359410
InputTypes: [
@@ -372,6 +423,11 @@ export function getHints() {
372423
text: 'CustomScalar',
373424
type: 'Scalar',
374425
},
426+
{
427+
description: '',
428+
text: 'LikeStorySubscriptionInput',
429+
type: 'Input',
430+
},
375431
{
376432
description: 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.',
377433
text: 'String',
@@ -464,6 +520,14 @@ export function getHints() {
464520
},
465521
],
466522

523+
PossibleSubscriptions: [
524+
{
525+
description: 'Like story subscription',
526+
text: 'LikeStory',
527+
type: 'LikeStorySubscriptionPayload',
528+
},
529+
],
530+
467531
QueryFields: [
468532
{
469533
description: '',

src/query/_shared/Parsers/QueryParser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ const parserOptions = {
6161
return 'Query';
6262
case 'mutation':
6363
return 'Mutation';
64+
case 'subscription':
65+
return 'Subscription';
6466
case 'fragment':
6567
return 'FragmentDefinition';
6668
default:

src/query/commands/__tests__/__snapshots__/getHintsAtPosition.test.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,13 @@ Array [
129129
},
130130
]
131131
`;
132+
133+
exports[`subscription field args 1`] = `
134+
Array [
135+
Object {
136+
"description": "",
137+
"text": "input",
138+
"type": "LikeStorySubscriptionInput",
139+
},
140+
]
141+
`;

src/query/commands/__tests__/__snapshots__/getInfoOfTokenAtPosition.test.js.snap

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,40 @@ type Query {
267267
],
268268
}
269269
`;
270+
271+
exports[`subscriptions field: Include both input and output type 1`] = `
272+
Object {
273+
"contents": Array [
274+
"# Like story subscription
275+
(field) LikeStory(input: LikeStorySubscriptionInput): LikeStorySubscriptionPayload",
276+
"input LikeStorySubscriptionInput {
277+
clientSubscriptionId: String
278+
id: ID!
279+
}",
280+
"type LikeStorySubscriptionPayload {
281+
clientSubscriptionId: String
282+
doesViewerLike: Boolean
283+
}",
284+
],
285+
}
286+
`;
287+
288+
exports[`subscriptions input object fields 1`] = `
289+
Object {
290+
"contents": Array [
291+
"(field) id: ID!",
292+
"# The \`ID\` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as \`\\"4\\"\`) or integer (such as \`4\`) input value will be accepted as an ID.",
293+
],
294+
}
295+
`;
296+
297+
exports[`subscriptions type: include description 1`] = `
298+
Object {
299+
"contents": Array [
300+
"type Subscription {
301+
# Like story subscription
302+
LikeStory(input: LikeStorySubscriptionInput): LikeStorySubscriptionPayload
303+
}",
304+
],
305+
}
306+
`;

src/query/commands/__tests__/getDefinitionAtPosition.test.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,5 +283,48 @@ describe('getDef', () => {
283283
).toEqual(null);
284284
});
285285
});
286-
});
287286

287+
describe('subscriptions', () => {
288+
it('subscription keyword', () => {
289+
const { sourceText, position } = code(`
290+
const a = Relay.QL\`
291+
subscription { LikeStory }
292+
#--^
293+
\`
294+
`);
295+
expect(
296+
getDefinitionAtPosition(schema, sourceText, position, relayQLParser),
297+
).toEqual(defLocations.Subscription);
298+
});
299+
300+
it('field', () => {
301+
const { sourceText, position } = code(`
302+
const a = Relay.QL\`
303+
subscription { LikeStory }
304+
#----------^
305+
\`
306+
`);
307+
expect(
308+
getDefinitionAtPosition(schema, sourceText, position, relayQLParser),
309+
).toEqual(defLocations.Subscription_LikeStory);
310+
});
311+
312+
it('input object fields', () => {
313+
const { sourceText, position } = code(`
314+
subscription {
315+
LikeStory(
316+
input: {
317+
id: "some_id",
318+
#----^
319+
}
320+
) {
321+
clientSubscriptionId
322+
}
323+
}
324+
`);
325+
expect(
326+
getDefinitionAtPosition(schema, sourceText, position, 'QueryParser'),
327+
).toEqual(defLocations.Subscription_LikeStorySubscriptionInput_id);
328+
});
329+
});
330+
});

src/query/commands/__tests__/getHintsAtPosition.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,30 @@ describe('show meta field __typename in abstract types', () => {
291291
).toMatchSnapshot();
292292
});
293293
});
294+
295+
describe('subscription', () => {
296+
it('fields', () => {
297+
const { sourceText, position } = code(`
298+
Relay.QL\`
299+
subscription { }
300+
--------------^
301+
\`;
302+
`);
303+
expect(
304+
getHintsAtPosition(schema, sourceText, position, relayConfig),
305+
).toEqual(hints.PossibleSubscriptions);
306+
});
307+
308+
it('field args', () => {
309+
const { sourceText, position } = code(`
310+
subscription {
311+
LikeStory()
312+
----------^
313+
}
314+
`);
315+
expect(
316+
getHintsAtPosition(schema, sourceText, position, queryConfig),
317+
).toMatchSnapshot();
318+
});
319+
});
320+

src/query/commands/__tests__/getInfoOfTokenAtPosition.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,54 @@ describe('mutations', () => {
228228
});
229229
});
230230

231+
describe('subscriptions', () => {
232+
it('type: include description', () => {
233+
const { sourceText, position } = code(`
234+
const a = Relay.QL\`
235+
subscription {
236+
#-----^
237+
}
238+
\`
239+
`);
240+
expect(
241+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
242+
).toMatchSnapshot();
243+
});
244+
245+
it('field: Include both input and output type', () => {
246+
const { sourceText, position } = code(`
247+
const a = Relay.QL\`
248+
subscription {
249+
LikeStory
250+
#-----^
251+
}
252+
\`
253+
`);
254+
expect(
255+
getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser),
256+
).toMatchSnapshot();
257+
});
258+
259+
it('input object fields', () => {
260+
const { sourceText, position } = code(`
261+
subscription {
262+
LikeStory(
263+
input: {
264+
id: ,
265+
#----^
266+
}
267+
) {
268+
clientSubscriptionId
269+
}
270+
}
271+
`);
272+
expect(
273+
getInfoOfTokenAtPosition(schema, sourceText, position, { parser: 'QueryParser' }),
274+
).toMatchSnapshot();
275+
});
276+
});
277+
278+
231279
describe('query', () => {
232280
it('query keyword', () => {
233281
const { sourceText, position } = code(`

src/query/commands/getDefinitionAtPosition.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function getDefinitionAtPosition(
4040
(state.kind === 'NamedType' && state.step === 0) ||
4141
(state.kind === 'TypeCondition' && state.step === 1) || // fragment on TypeName <----
4242
(state.kind === 'Mutation' && state.step === 0) || // ----> mutation { }
43+
(state.kind === 'Subscription' && state.step === 0) || // ----> subscription { }
4344
(state.kind === 'Query' && state.step === 0) // ----> query xyz { xyz }
4445
) {
4546
if (typeInfo.type) {

src/query/commands/getInfoOfTokenAtPosition.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function getInfoOfTokenAtPosition( // eslint-disable-line complexity
3737
(kind === 'NamedType' && step === 0) ||
3838
(kind === 'TypeCondition' && step === 1) || // fragment on TypeName <----
3939
(kind === 'Mutation' && step === 0) || // ----> mutation { }
40+
(kind === 'Subscription' && step === 0) || // ----> subscription { }
4041
(kind === 'Query' && step === 0) // ----> query xyz { xyz }
4142
) {
4243
if (typeInfo.type) {
@@ -57,7 +58,7 @@ function getInfoOfTokenAtPosition( // eslint-disable-line complexity
5758

5859
contents.push(fieldDef.print());
5960

60-
if (typeInfo.parentType && typeInfo.parentType.name === 'Mutation') {
61+
if (typeInfo.parentType && (typeInfo.parentType.name === 'Mutation' || typeInfo.parentType.name === 'Subscription')) {
6162
// include input args type
6263
fieldDef.args.forEach((arg) => {
6364
const argType = getNamedType(arg.type);

0 commit comments

Comments
 (0)