From 2da1800055f6f5fc68308d64e6e8c9380415b742 Mon Sep 17 00:00:00 2001 From: Mayank Agarwal Date: Sat, 9 Dec 2017 15:50:53 +0530 Subject: [PATCH] feat(subscriptions): add support for subscriptions. closes #79 --- src/__test-data__/schema/subscription.gql | 14 ++++ src/__test-data__/utils.js | 64 +++++++++++++++++++ src/query/_shared/Parsers/QueryParser.js | 2 + .../getHintsAtPosition.test.js.snap | 10 +++ .../getInfoOfTokenAtPosition.test.js.snap | 37 +++++++++++ .../__tests__/getDefinitionAtPosition.test.js | 45 ++++++++++++- .../__tests__/getHintsAtPosition.test.js | 27 ++++++++ .../getInfoOfTokenAtPosition.test.js | 48 ++++++++++++++ src/query/commands/getDefinitionAtPosition.js | 1 + .../commands/getInfoOfTokenAtPosition.js | 3 +- 10 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 src/__test-data__/schema/subscription.gql diff --git a/src/__test-data__/schema/subscription.gql b/src/__test-data__/schema/subscription.gql new file mode 100644 index 0000000..81f63af --- /dev/null +++ b/src/__test-data__/schema/subscription.gql @@ -0,0 +1,14 @@ +type Subscription { + # Like story subscription + LikeStory(input: LikeStorySubscriptionInput): LikeStorySubscriptionPayload +} + +input LikeStorySubscriptionInput { + clientSubscriptionId: String + id: ID! +} + +type LikeStorySubscriptionPayload { + clientSubscriptionId: String + doesViewerLike: Boolean +} diff --git a/src/__test-data__/utils.js b/src/__test-data__/utils.js index 2919ed1..ea5c23b 100644 --- a/src/__test-data__/utils.js +++ b/src/__test-data__/utils.js @@ -68,6 +68,27 @@ export function getDefLocations() { path: path.resolve('src/__test-data__/schema/mutation.gql'), }, + Subscription: { + start: { line: 1, column: 1 }, + end: { line: 4, column: 2 }, + path: path.resolve('src/__test-data__/schema/subscription.gql'), + }, + Subscription_LikeStory: { // eslint-disable-line camelcase + start: { line: 3, column: 3 }, + end: { line: 3, column: 77 }, + path: path.resolve('src/__test-data__/schema/subscription.gql'), + }, + Subscription_LikeStorySubscriptionInput: { // eslint-disable-line camelcase + start: { line: 6, column: 1 }, + end: { line: 9, column: 2 }, + path: path.resolve('src/__test-data__/schema/subscription.gql'), + }, + Subscription_LikeStorySubscriptionInput_id: { // eslint-disable-line camelcase + start: { line: 8, column: 3 }, + end: { line: 8, column: 10 }, + path: path.resolve('src/__test-data__/schema/subscription.gql'), + }, + Query: { start: { line: 2, column: 1 }, end: { line: 6, column: 2 }, @@ -213,6 +234,16 @@ export function getHints() { text: 'NewPlayer', type: 'Object', }, + { + description: '', + text: 'Subscription', + type: 'Object', + }, + { + description: '', + text: 'LikeStorySubscriptionPayload', + type: 'Object', + }, ], OutputTypes: [ @@ -276,6 +307,16 @@ export function getHints() { text: 'CustomScalar', type: 'Scalar', }, + { + description: '', + text: 'Subscription', + type: 'Object', + }, + { + description: '', + text: 'LikeStorySubscriptionPayload', + type: 'Object', + }, { 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.', text: 'String', @@ -354,6 +395,16 @@ export function getHints() { text: 'Entity', type: 'Union', }, + { + description: '', + text: 'Subscription', + type: 'Object', + }, + { + description: '', + text: 'LikeStorySubscriptionPayload', + type: 'Object', + }, ], InputTypes: [ @@ -372,6 +423,11 @@ export function getHints() { text: 'CustomScalar', type: 'Scalar', }, + { + description: '', + text: 'LikeStorySubscriptionInput', + type: 'Input', + }, { 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.', text: 'String', @@ -464,6 +520,14 @@ export function getHints() { }, ], + PossibleSubscriptions: [ + { + description: 'Like story subscription', + text: 'LikeStory', + type: 'LikeStorySubscriptionPayload', + }, + ], + QueryFields: [ { description: '', diff --git a/src/query/_shared/Parsers/QueryParser.js b/src/query/_shared/Parsers/QueryParser.js index 04dc8b1..76ce508 100644 --- a/src/query/_shared/Parsers/QueryParser.js +++ b/src/query/_shared/Parsers/QueryParser.js @@ -61,6 +61,8 @@ const parserOptions = { return 'Query'; case 'mutation': return 'Mutation'; + case 'subscription': + return 'Subscription'; case 'fragment': return 'FragmentDefinition'; default: diff --git a/src/query/commands/__tests__/__snapshots__/getHintsAtPosition.test.js.snap b/src/query/commands/__tests__/__snapshots__/getHintsAtPosition.test.js.snap index 79489dd..1805cfe 100644 --- a/src/query/commands/__tests__/__snapshots__/getHintsAtPosition.test.js.snap +++ b/src/query/commands/__tests__/__snapshots__/getHintsAtPosition.test.js.snap @@ -129,3 +129,13 @@ Array [ }, ] `; + +exports[`subscription field args 1`] = ` +Array [ + Object { + "description": "", + "text": "input", + "type": "LikeStorySubscriptionInput", + }, +] +`; diff --git a/src/query/commands/__tests__/__snapshots__/getInfoOfTokenAtPosition.test.js.snap b/src/query/commands/__tests__/__snapshots__/getInfoOfTokenAtPosition.test.js.snap index 3ed2f14..3b933ed 100644 --- a/src/query/commands/__tests__/__snapshots__/getInfoOfTokenAtPosition.test.js.snap +++ b/src/query/commands/__tests__/__snapshots__/getInfoOfTokenAtPosition.test.js.snap @@ -267,3 +267,40 @@ type Query { ], } `; + +exports[`subscriptions field: Include both input and output type 1`] = ` +Object { + "contents": Array [ + "# Like story subscription +(field) LikeStory(input: LikeStorySubscriptionInput): LikeStorySubscriptionPayload", + "input LikeStorySubscriptionInput { + clientSubscriptionId: String + id: ID! +}", + "type LikeStorySubscriptionPayload { + clientSubscriptionId: String + doesViewerLike: Boolean +}", + ], +} +`; + +exports[`subscriptions input object fields 1`] = ` +Object { + "contents": Array [ + "(field) id: ID!", + "# 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.", + ], +} +`; + +exports[`subscriptions type: include description 1`] = ` +Object { + "contents": Array [ + "type Subscription { + # Like story subscription + LikeStory(input: LikeStorySubscriptionInput): LikeStorySubscriptionPayload +}", + ], +} +`; diff --git a/src/query/commands/__tests__/getDefinitionAtPosition.test.js b/src/query/commands/__tests__/getDefinitionAtPosition.test.js index a8440d8..5d8bcae 100644 --- a/src/query/commands/__tests__/getDefinitionAtPosition.test.js +++ b/src/query/commands/__tests__/getDefinitionAtPosition.test.js @@ -283,5 +283,48 @@ describe('getDef', () => { ).toEqual(null); }); }); -}); + describe('subscriptions', () => { + it('subscription keyword', () => { + const { sourceText, position } = code(` + const a = Relay.QL\` + subscription { LikeStory } + #--^ + \` + `); + expect( + getDefinitionAtPosition(schema, sourceText, position, relayQLParser), + ).toEqual(defLocations.Subscription); + }); + + it('field', () => { + const { sourceText, position } = code(` + const a = Relay.QL\` + subscription { LikeStory } + #----------^ + \` + `); + expect( + getDefinitionAtPosition(schema, sourceText, position, relayQLParser), + ).toEqual(defLocations.Subscription_LikeStory); + }); + + it('input object fields', () => { + const { sourceText, position } = code(` + subscription { + LikeStory( + input: { + id: "some_id", + #----^ + } + ) { + clientSubscriptionId + } + } + `); + expect( + getDefinitionAtPosition(schema, sourceText, position, 'QueryParser'), + ).toEqual(defLocations.Subscription_LikeStorySubscriptionInput_id); + }); + }); +}); diff --git a/src/query/commands/__tests__/getHintsAtPosition.test.js b/src/query/commands/__tests__/getHintsAtPosition.test.js index 18a8f77..5321ad0 100644 --- a/src/query/commands/__tests__/getHintsAtPosition.test.js +++ b/src/query/commands/__tests__/getHintsAtPosition.test.js @@ -291,3 +291,30 @@ describe('show meta field __typename in abstract types', () => { ).toMatchSnapshot(); }); }); + +describe('subscription', () => { + it('fields', () => { + const { sourceText, position } = code(` + Relay.QL\` + subscription { } + --------------^ + \`; + `); + expect( + getHintsAtPosition(schema, sourceText, position, relayConfig), + ).toEqual(hints.PossibleSubscriptions); + }); + + it('field args', () => { + const { sourceText, position } = code(` + subscription { + LikeStory() + ----------^ + } + `); + expect( + getHintsAtPosition(schema, sourceText, position, queryConfig), + ).toMatchSnapshot(); + }); +}); + diff --git a/src/query/commands/__tests__/getInfoOfTokenAtPosition.test.js b/src/query/commands/__tests__/getInfoOfTokenAtPosition.test.js index 73014b6..752a5d6 100644 --- a/src/query/commands/__tests__/getInfoOfTokenAtPosition.test.js +++ b/src/query/commands/__tests__/getInfoOfTokenAtPosition.test.js @@ -228,6 +228,54 @@ describe('mutations', () => { }); }); +describe('subscriptions', () => { + it('type: include description', () => { + const { sourceText, position } = code(` + const a = Relay.QL\` + subscription { + #-----^ + } + \` + `); + expect( + getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser), + ).toMatchSnapshot(); + }); + + it('field: Include both input and output type', () => { + const { sourceText, position } = code(` + const a = Relay.QL\` + subscription { + LikeStory + #-----^ + } + \` + `); + expect( + getInfoOfTokenAtPosition(schema, sourceText, position, relayQLParser), + ).toMatchSnapshot(); + }); + + it('input object fields', () => { + const { sourceText, position } = code(` + subscription { + LikeStory( + input: { + id: , + #----^ + } + ) { + clientSubscriptionId + } + } + `); + expect( + getInfoOfTokenAtPosition(schema, sourceText, position, { parser: 'QueryParser' }), + ).toMatchSnapshot(); + }); +}); + + describe('query', () => { it('query keyword', () => { const { sourceText, position } = code(` diff --git a/src/query/commands/getDefinitionAtPosition.js b/src/query/commands/getDefinitionAtPosition.js index f2e9770..d830fa6 100644 --- a/src/query/commands/getDefinitionAtPosition.js +++ b/src/query/commands/getDefinitionAtPosition.js @@ -40,6 +40,7 @@ function getDefinitionAtPosition( (state.kind === 'NamedType' && state.step === 0) || (state.kind === 'TypeCondition' && state.step === 1) || // fragment on TypeName <---- (state.kind === 'Mutation' && state.step === 0) || // ----> mutation { } + (state.kind === 'Subscription' && state.step === 0) || // ----> subscription { } (state.kind === 'Query' && state.step === 0) // ----> query xyz { xyz } ) { if (typeInfo.type) { diff --git a/src/query/commands/getInfoOfTokenAtPosition.js b/src/query/commands/getInfoOfTokenAtPosition.js index 867a34a..c6fa111 100644 --- a/src/query/commands/getInfoOfTokenAtPosition.js +++ b/src/query/commands/getInfoOfTokenAtPosition.js @@ -37,6 +37,7 @@ function getInfoOfTokenAtPosition( // eslint-disable-line complexity (kind === 'NamedType' && step === 0) || (kind === 'TypeCondition' && step === 1) || // fragment on TypeName <---- (kind === 'Mutation' && step === 0) || // ----> mutation { } + (kind === 'Subscription' && step === 0) || // ----> subscription { } (kind === 'Query' && step === 0) // ----> query xyz { xyz } ) { if (typeInfo.type) { @@ -57,7 +58,7 @@ function getInfoOfTokenAtPosition( // eslint-disable-line complexity contents.push(fieldDef.print()); - if (typeInfo.parentType && typeInfo.parentType.name === 'Mutation') { + if (typeInfo.parentType && (typeInfo.parentType.name === 'Mutation' || typeInfo.parentType.name === 'Subscription')) { // include input args type fieldDef.args.forEach((arg) => { const argType = getNamedType(arg.type);