vscode-apollo 1.20.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +27 -18
- package/.git-blame-ignore-revs +2 -0
- package/.nvmrc +1 -1
- package/.vscode/launch.json +9 -4
- package/.vscode/tasks.json +58 -16
- package/.vscodeignore +12 -1
- package/CHANGELOG.md +78 -0
- package/CODEOWNERS +4 -0
- package/README.md +97 -48
- package/graphql.configuration.json +5 -1
- package/images/marketplace/apollo-wordmark.png +0 -0
- package/jest.config.ts +14 -4
- package/jest.e2e.config.js +17 -0
- package/package.json +67 -68
- package/renovate.json +7 -0
- package/sampleWorkspace/clientSchema/apollo.config.cjs +10 -0
- package/sampleWorkspace/clientSchema/src/clientSchema.js +16 -0
- package/sampleWorkspace/clientSchema/src/test.js +18 -0
- package/sampleWorkspace/fixtures/starwarsSchema.graphql +299 -0
- package/sampleWorkspace/httpSchema/apollo.config.ts +8 -0
- package/sampleWorkspace/httpSchema/src/test.js +9 -0
- package/sampleWorkspace/localSchema/apollo.config.js +8 -0
- package/sampleWorkspace/localSchema/src/test.js +8 -0
- package/sampleWorkspace/localSchemaArray/apollo.config.js +12 -0
- package/sampleWorkspace/localSchemaArray/planets.graphql +20 -0
- package/sampleWorkspace/localSchemaArray/src/test.js +12 -0
- package/sampleWorkspace/sampleWorkspace.code-workspace +20 -0
- package/sampleWorkspace/spotifyGraph/apollo.config.mjs +5 -0
- package/sampleWorkspace/spotifyGraph/src/test.js +11 -0
- package/src/__e2e__/mockServer.js +117 -0
- package/src/__e2e__/mocks.js +13094 -0
- package/src/__e2e__/run.js +23 -0
- package/src/__e2e__/runTests.js +44 -0
- package/src/__e2e__/setup.js +1 -0
- package/src/__e2e__/vscode-environment.js +16 -0
- package/src/__e2e__/vscode.js +1 -0
- package/src/build.js +57 -0
- package/src/env/index.ts +0 -3
- package/src/extension.ts +251 -225
- package/src/language-server/__e2e__/clientSchema.e2e.ts +147 -0
- package/src/language-server/__e2e__/httpSchema.e2e.ts +21 -0
- package/src/language-server/__e2e__/localSchema.e2e.ts +25 -0
- package/src/language-server/__e2e__/localSchemaArray.e2e.ts +31 -0
- package/src/language-server/__e2e__/studioGraph.e2e.ts +65 -0
- package/src/language-server/__e2e__/utils.ts +151 -0
- package/src/language-server/__tests__/diagnostics.test.ts +8 -8
- package/src/language-server/__tests__/fileSet.test.ts +1 -1
- package/src/language-server/__tests__/fixtures/starwarsSchema.ts +2 -2
- package/src/language-server/config/__tests__/config.ts +22 -96
- package/src/language-server/config/__tests__/loadConfig.ts +97 -221
- package/src/language-server/config/__tests__/utils.ts +22 -29
- package/src/language-server/config/config.ts +221 -156
- package/src/language-server/config/loadConfig.ts +26 -153
- package/src/language-server/config/utils.ts +5 -16
- package/src/language-server/diagnostics.ts +17 -8
- package/src/language-server/document.ts +16 -16
- package/src/language-server/engine/index.ts +57 -39
- package/src/language-server/engine/operations/frontendUrlRoot.ts +9 -1
- package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +9 -1
- package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +10 -5
- package/src/language-server/errors/logger.ts +1 -1
- package/src/language-server/errors/validation.ts +20 -23
- package/src/language-server/fileSet.ts +10 -12
- package/src/language-server/format.ts +1 -1
- package/src/language-server/graphqlTypes.ts +13020 -3455
- package/src/language-server/index.ts +0 -1
- package/src/language-server/languageProvider.ts +29 -32
- package/src/language-server/loadingHandler.ts +10 -27
- package/src/language-server/project/base.ts +32 -25
- package/src/language-server/project/client.ts +80 -114
- package/src/language-server/project/defaultClientSchema.ts +29 -4
- package/src/language-server/providers/schema/__tests__/file.ts +60 -19
- package/src/language-server/providers/schema/base.ts +2 -2
- package/src/language-server/providers/schema/endpoint.ts +15 -34
- package/src/language-server/providers/schema/engine.ts +25 -18
- package/src/language-server/providers/schema/file.ts +41 -32
- package/src/language-server/providers/schema/index.ts +5 -21
- package/src/language-server/server.ts +72 -50
- package/src/language-server/typings/graphql.d.ts +3 -3
- package/src/language-server/utilities/__tests__/graphql.test.ts +42 -54
- package/src/language-server/utilities/debouncer.ts +1 -1
- package/src/language-server/utilities/debug.ts +6 -5
- package/src/language-server/utilities/graphql.ts +17 -16
- package/src/language-server/utilities/source.ts +16 -16
- package/src/language-server/utilities/uri.ts +2 -2
- package/src/language-server/workspace.ts +29 -37
- package/src/languageServerClient.ts +4 -4
- package/src/messages.ts +38 -47
- package/src/tools/__tests__/buildServiceDefinition.test.ts +2 -2
- package/src/tools/buildServiceDefinition.ts +11 -11
- package/src/tools/schema/resolveObject.ts +1 -1
- package/src/tools/utilities/predicates.ts +1 -1
- package/src/utils.ts +7 -6
- package/syntaxes/graphql.dart.json +2 -4
- package/syntaxes/graphql.ex.json +1 -4
- package/tsconfig.build.json +8 -1
- package/tsconfig.json +5 -3
- package/src/env/fetch/fetch.ts +0 -32
- package/src/env/fetch/global.ts +0 -30
- package/src/env/fetch/index.d.ts +0 -2
- package/src/env/fetch/index.ts +0 -2
- package/src/env/fetch/url.ts +0 -9
- package/src/env/polyfills/array.ts +0 -17
- package/src/env/polyfills/index.ts +0 -2
- package/src/env/polyfills/object.ts +0 -7
- package/src/language-server/engine/GraphQLDataSource.ts +0 -124
- package/src/language-server/project/service.ts +0 -48
- package/src/language-server/typings/codemirror.d.ts +0 -4
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { TextEditor } from "vscode";
|
|
2
|
+
import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
|
|
3
|
+
|
|
4
|
+
let editor: TextEditor;
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
closeAllEditors();
|
|
7
|
+
editor = await openEditor("clientSchema/src/test.js");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("completion", async () => {
|
|
11
|
+
// dro|id
|
|
12
|
+
await testCompletion(editor, [4, 7], [["droid", "Droid"]]);
|
|
13
|
+
// na|me
|
|
14
|
+
await testCompletion(editor, [5, 8], [["name", "String!"]]);
|
|
15
|
+
// mo|del
|
|
16
|
+
await testCompletion(editor, [6, 8], [["model", "String"]]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("hover", async () => {
|
|
20
|
+
// featu|reFlagDefer
|
|
21
|
+
expect(await getHover(editor, [3, 10])).toMatchInlineSnapshot(`
|
|
22
|
+
"\`\`\`graphql
|
|
23
|
+
Query.featureFlagDefer: Boolean!
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
\`Client-Only Field\` \`Resolved locally\`
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
Whether to use defer"
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
// @c|lient(always: false)
|
|
36
|
+
expect(await getHover(editor, [3, 24])).toMatchInlineSnapshot(`
|
|
37
|
+
"\`\`\`graphql
|
|
38
|
+
@client(always: Boolean)
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
Direct the client to resolve this field locally, either from the cache or local resolvers."
|
|
44
|
+
`);
|
|
45
|
+
|
|
46
|
+
// @client(alwa|ys: false)
|
|
47
|
+
expect(await getHover(editor, [3, 33])).toMatchInlineSnapshot(`
|
|
48
|
+
"\`\`\`graphql
|
|
49
|
+
always: Boolean
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
When true, the client will never use the cache for this value. See
|
|
55
|
+
https://www.apollographql.com/docs/react/local-state/local-resolvers/#forcing-resolvers-with-clientalways-true"
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
// @expo|rt(as: "defer")
|
|
59
|
+
expect(await getHover(editor, [3, 49])).toMatchInlineSnapshot(`
|
|
60
|
+
"\`\`\`graphql
|
|
61
|
+
@export(as: String!)
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
Export this locally resolved field as a variable to be used in the remainder of this query. See
|
|
67
|
+
https://www.apollographql.com/docs/react/local-state/local-resolvers/#using-client-fields-as-variables"
|
|
68
|
+
`);
|
|
69
|
+
expect(await getHover(editor, [3, 53])).toMatchInlineSnapshot(`
|
|
70
|
+
"\`\`\`graphql
|
|
71
|
+
as: String!
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
The variable name to export this field as."
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
// @nonre|active
|
|
80
|
+
expect(await getHover(editor, [7, 28])).toMatchInlineSnapshot(`
|
|
81
|
+
"\`\`\`graphql
|
|
82
|
+
@nonreactive
|
|
83
|
+
\`\`\`
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
The @nonreactive directive can be used to mark query fields or fragment spreads and is used to indicate that changes to the data contained within the subtrees marked @nonreactive should not trigger rerendering.
|
|
88
|
+
This allows parent components to fetch data to be rendered by their children without rerendering themselves when the data corresponding with fields marked as @nonreactive change.
|
|
89
|
+
https://www.apollographql.com/docs/react/data/directives#nonreactive"
|
|
90
|
+
`);
|
|
91
|
+
|
|
92
|
+
// @def|er(if: $defer, label: "fc")
|
|
93
|
+
expect(await getHover(editor, [8, 14])).toMatchInlineSnapshot(`
|
|
94
|
+
"\`\`\`graphql
|
|
95
|
+
@defer(if: Boolean, label: String)
|
|
96
|
+
\`\`\`
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
This directive enables your queries to receive data for specific fields incrementally, instead of receiving all field data at the same time.
|
|
101
|
+
This is helpful whenever some fields in a query take much longer to resolve than others.
|
|
102
|
+
https://www.apollographql.com/docs/react/data/directives#defer"
|
|
103
|
+
`);
|
|
104
|
+
//@defer(i|f: $defer, label: "fc")
|
|
105
|
+
expect(await getHover(editor, [8, 18])).toMatchInlineSnapshot(`
|
|
106
|
+
"\`\`\`graphql
|
|
107
|
+
if: Boolean
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
When true fragment may be deferred, if omitted defaults to true."
|
|
113
|
+
`);
|
|
114
|
+
//@defer(if: $defer, labe|l: "fc")
|
|
115
|
+
expect(await getHover(editor, [8, 33])).toMatchInlineSnapshot(`
|
|
116
|
+
"\`\`\`graphql
|
|
117
|
+
label: String
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
A unique label across all @defer and @stream directives in an operation.
|
|
123
|
+
This label should be used by GraphQL clients to identify the data from patch responses and associate it with the correct fragment.
|
|
124
|
+
If provided, the GraphQL Server must add it to the payload."
|
|
125
|
+
`);
|
|
126
|
+
// @connec|tion(key: "feed")
|
|
127
|
+
expect(await getHover(editor, [9, 53])).toMatchInlineSnapshot(`
|
|
128
|
+
"\`\`\`graphql
|
|
129
|
+
@connection(key: String!, filter: [String!])
|
|
130
|
+
\`\`\`
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
Specify a custom store key for this result. See
|
|
135
|
+
https://www.apollographql.com/docs/react/caching/advanced-topics/#the-connection-directive"
|
|
136
|
+
`);
|
|
137
|
+
// @connection(ke|y: "feed")
|
|
138
|
+
expect(await getHover(editor, [9, 61])).toMatchInlineSnapshot(`
|
|
139
|
+
"\`\`\`graphql
|
|
140
|
+
key: String!
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
Specify the store key."
|
|
146
|
+
`);
|
|
147
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TextEditor } from "vscode";
|
|
2
|
+
import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
|
|
3
|
+
|
|
4
|
+
let editor: TextEditor;
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
closeAllEditors();
|
|
7
|
+
editor = await openEditor("httpSchema/src/test.js");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("completion", async () => {
|
|
11
|
+
await testCompletion(editor, [3, 7], [["books", "[Book]"]]);
|
|
12
|
+
await testCompletion(editor, [5, 9], [["author", "String"]]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("hover", async () => {
|
|
16
|
+
expect(await getHover(editor, [5, 9])).toMatchInlineSnapshot(`
|
|
17
|
+
"\`\`\`graphql
|
|
18
|
+
Book.author: String
|
|
19
|
+
\`\`\`"
|
|
20
|
+
`);
|
|
21
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TextEditor } from "vscode";
|
|
2
|
+
import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
|
|
3
|
+
|
|
4
|
+
let editor: TextEditor;
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
closeAllEditors();
|
|
7
|
+
editor = await openEditor("localSchema/src/test.js");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("completion", async () => {
|
|
11
|
+
await testCompletion(editor, [3, 7], [["droid", "Droid"]]);
|
|
12
|
+
await testCompletion(editor, [4, 8], [["name", "String!"]]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("hover", async () => {
|
|
16
|
+
expect(await getHover(editor, [4, 8])).toMatchInlineSnapshot(`
|
|
17
|
+
"\`\`\`graphql
|
|
18
|
+
Droid.name: String!
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
What others call this droid"
|
|
24
|
+
`);
|
|
25
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TextEditor } from "vscode";
|
|
2
|
+
import { closeAllEditors, openEditor, testCompletion, getHover } from "./utils";
|
|
3
|
+
|
|
4
|
+
let editor: TextEditor;
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
closeAllEditors();
|
|
7
|
+
editor = await openEditor("localSchemaArray/src/test.js");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("completion", async () => {
|
|
11
|
+
await testCompletion(editor, [3, 7], [["droid", "Droid"]]);
|
|
12
|
+
await testCompletion(editor, [4, 8], [["name", "String!"]]);
|
|
13
|
+
await testCompletion(editor, [6, 7], [["planets", "[Planet]"]]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("hover", async () => {
|
|
17
|
+
expect(await getHover(editor, [4, 8])).toMatchInlineSnapshot(`
|
|
18
|
+
"\`\`\`graphql
|
|
19
|
+
Droid.name: String!
|
|
20
|
+
\`\`\`
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
What others call this droid"
|
|
25
|
+
`);
|
|
26
|
+
expect(await getHover(editor, [6, 7])).toMatchInlineSnapshot(`
|
|
27
|
+
"\`\`\`graphql
|
|
28
|
+
Query.planets: [Planet]
|
|
29
|
+
\`\`\`"
|
|
30
|
+
`);
|
|
31
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { TextEditor } from "vscode";
|
|
2
|
+
import {
|
|
3
|
+
closeAllEditors,
|
|
4
|
+
openEditor,
|
|
5
|
+
testCompletion,
|
|
6
|
+
getHover,
|
|
7
|
+
getExtension,
|
|
8
|
+
getOutputChannelDocument,
|
|
9
|
+
reloadService,
|
|
10
|
+
} from "./utils";
|
|
11
|
+
import mocks from "../../__e2e__/mocks.js";
|
|
12
|
+
import vscode from "vscode";
|
|
13
|
+
import { scheduler } from "node:timers/promises";
|
|
14
|
+
|
|
15
|
+
const mockPort = Number(process.env.MOCK_SERVER_PORT);
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
closeAllEditors();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("completion", async () => {
|
|
21
|
+
const editor = await openEditor("spotifyGraph/src/test.js");
|
|
22
|
+
await testCompletion(editor, [4, 9], [["profile", "CurrentUserProfile!"]]);
|
|
23
|
+
await testCompletion(editor, [6, 15], [["displayName", "String"]]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("hover", async () => {
|
|
27
|
+
const editor = await openEditor("spotifyGraph/src/test.js");
|
|
28
|
+
expect(await getHover(editor, [4, 9])).toMatchInlineSnapshot(`
|
|
29
|
+
"\`\`\`graphql
|
|
30
|
+
CurrentUser.profile: CurrentUserProfile!
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
Get detailed profile information about the current user (including the current user's username)."
|
|
36
|
+
`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("wrong token", async () => {
|
|
40
|
+
try {
|
|
41
|
+
await mocks.sendMock(mockPort, mocks.GetSchemaByTag_WRONG_TOKEN);
|
|
42
|
+
await mocks.sendMock(mockPort, mocks.SchemaTagsAndFieldStats_WRONG_TOKEN);
|
|
43
|
+
|
|
44
|
+
const ext = getExtension();
|
|
45
|
+
ext.outputChannel.clear();
|
|
46
|
+
const outputDoc = await getOutputChannelDocument();
|
|
47
|
+
|
|
48
|
+
await reloadService();
|
|
49
|
+
const output = outputDoc.getText();
|
|
50
|
+
|
|
51
|
+
// currently, this logs twice, along with a full stracktrace, but no indication of where it came from
|
|
52
|
+
// this should be improved on.
|
|
53
|
+
expect(output).toContain(
|
|
54
|
+
`
|
|
55
|
+
[GraphQL error]: HTTP fetch failed from 'kotlin': 406: Not Acceptable
|
|
56
|
+
[GraphQL error]: Invalid credentials provided
|
|
57
|
+
ApolloError: HTTP fetch failed from 'kotlin': 406: Not Acceptable
|
|
58
|
+
Invalid credentials provided
|
|
59
|
+
at new ApolloError`.trim(),
|
|
60
|
+
);
|
|
61
|
+
} finally {
|
|
62
|
+
await mocks.loadDefaultMocks(mockPort);
|
|
63
|
+
await reloadService();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as vscode from "vscode";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { scheduler } from "node:timers/promises";
|
|
4
|
+
import { ProjectStats } from "src/messages";
|
|
5
|
+
import { VSCodeGraphQLExtension } from "src/extension";
|
|
6
|
+
|
|
7
|
+
function resolve(file: string) {
|
|
8
|
+
return join(__dirname, "..", "..", "..", "sampleWorkspace", file);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function closeAllEditors() {
|
|
12
|
+
while (vscode.window.visibleTextEditors.length > 0) {
|
|
13
|
+
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function waitFor<T>(
|
|
18
|
+
fn: () => Promise<T>,
|
|
19
|
+
{ retries = 15, delay = 200 } = {},
|
|
20
|
+
) {
|
|
21
|
+
for (let i = 0; i < retries; i++) {
|
|
22
|
+
try {
|
|
23
|
+
return await fn();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (i === retries - 1) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
await scheduler.wait(delay);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw "unreachable";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function openEditor(file: string) {
|
|
35
|
+
const textDocument = await vscode.workspace.openTextDocument(resolve(file));
|
|
36
|
+
const editor = await vscode.window.showTextDocument(textDocument);
|
|
37
|
+
await waitForLSP(file);
|
|
38
|
+
return editor;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function waitForLSP(file: string) {
|
|
42
|
+
return waitFor(async () => {
|
|
43
|
+
const uri = vscode.Uri.file(resolve(file));
|
|
44
|
+
const stats = await vscode.commands.executeCommand<ProjectStats>(
|
|
45
|
+
"apollographql/fileStats",
|
|
46
|
+
uri.toString(),
|
|
47
|
+
);
|
|
48
|
+
expect(stats.loaded).toBe(true);
|
|
49
|
+
return stats;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function testCompletion(
|
|
54
|
+
editor: vscode.TextEditor,
|
|
55
|
+
[line, character]: [number, number],
|
|
56
|
+
expected: Array<[label: string, detail: string]>,
|
|
57
|
+
) {
|
|
58
|
+
await waitFor(async () => {
|
|
59
|
+
editor.selection = new vscode.Selection(line, character, line, character);
|
|
60
|
+
// without this, the completion list is not updated
|
|
61
|
+
await scheduler.wait(300);
|
|
62
|
+
const completions =
|
|
63
|
+
await vscode.commands.executeCommand<vscode.CompletionList>(
|
|
64
|
+
"vscode.executeCompletionItemProvider",
|
|
65
|
+
editor.document.uri,
|
|
66
|
+
new vscode.Position(line, character),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const labels = completions.items.slice(0, expected.length).map((item) =>
|
|
70
|
+
typeof item.label === "string"
|
|
71
|
+
? { label: item.label, detail: "" }
|
|
72
|
+
: {
|
|
73
|
+
label: item.label.label,
|
|
74
|
+
detail: item.detail,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
expect(labels).toStrictEqual(
|
|
78
|
+
expected.map(([label, detail]) => ({ label, detail })),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function getHover(
|
|
84
|
+
editor: vscode.TextEditor,
|
|
85
|
+
[line, character]: [number, number],
|
|
86
|
+
) {
|
|
87
|
+
editor.selection = new vscode.Selection(line, character, line, character);
|
|
88
|
+
// without this, the completion list is not updated
|
|
89
|
+
await scheduler.wait(300);
|
|
90
|
+
const hovers = await vscode.commands.executeCommand<vscode.Hover[]>(
|
|
91
|
+
"vscode.executeHoverProvider",
|
|
92
|
+
editor.document.uri,
|
|
93
|
+
new vscode.Position(line, character),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const item = hovers[0];
|
|
97
|
+
const content = item.contents[0];
|
|
98
|
+
const label = typeof content === "string" ? content : content.value;
|
|
99
|
+
return label;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getExtension(): VSCodeGraphQLExtension {
|
|
103
|
+
return vscode.extensions.getExtension("apollographql.vscode-apollo")!.exports;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function getOutputChannelDocument() {
|
|
107
|
+
const ext = getExtension();
|
|
108
|
+
ext.outputChannel.show();
|
|
109
|
+
await scheduler.wait(300);
|
|
110
|
+
const doc = vscode.workspace.textDocuments.find((d) =>
|
|
111
|
+
d.uri.path.startsWith("extension-output-apollographql.vscode-apollo"),
|
|
112
|
+
);
|
|
113
|
+
if (!doc) {
|
|
114
|
+
throw new Error("Output channel document not found");
|
|
115
|
+
}
|
|
116
|
+
return doc;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getReloadPromise() {
|
|
120
|
+
const disposables: vscode.Disposable[] = [];
|
|
121
|
+
const ext = getExtension();
|
|
122
|
+
const waitingTokens = new Set<number>();
|
|
123
|
+
disposables.push(
|
|
124
|
+
ext.client.onNotification(
|
|
125
|
+
ext.LanguageServerNotifications.Loading,
|
|
126
|
+
({ token }) => {
|
|
127
|
+
waitingTokens.add(token);
|
|
128
|
+
},
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
return new Promise<void>((resolve) => {
|
|
132
|
+
disposables.push(
|
|
133
|
+
ext.client.onNotification(
|
|
134
|
+
ext.LanguageServerNotifications.LoadingComplete,
|
|
135
|
+
(token) => {
|
|
136
|
+
waitingTokens.delete(token);
|
|
137
|
+
if (waitingTokens.size === 0) resolve();
|
|
138
|
+
},
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
}).finally(() => disposables.forEach((d) => d.dispose()));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function reloadService() {
|
|
145
|
+
const reloaded = getReloadPromise();
|
|
146
|
+
|
|
147
|
+
vscode.commands.executeCommand("apollographql/reloadService");
|
|
148
|
+
|
|
149
|
+
await reloaded;
|
|
150
|
+
await scheduler.wait(100);
|
|
151
|
+
}
|
|
@@ -14,7 +14,7 @@ const validDocument = new GraphQLDocument(
|
|
|
14
14
|
name
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
}`)
|
|
17
|
+
}`),
|
|
18
18
|
);
|
|
19
19
|
const invalidDocument = new GraphQLDocument(
|
|
20
20
|
new Source(`
|
|
@@ -25,7 +25,7 @@ const invalidDocument = new GraphQLDocument(
|
|
|
25
25
|
name
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
}`)
|
|
28
|
+
}`),
|
|
29
29
|
);
|
|
30
30
|
const documentWithTypes = new GraphQLDocument(
|
|
31
31
|
new Source(`
|
|
@@ -43,41 +43,41 @@ const documentWithTypes = new GraphQLDocument(
|
|
|
43
43
|
name
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
}`)
|
|
46
|
+
}`),
|
|
47
47
|
);
|
|
48
48
|
const documentWithOffset = new GraphQLDocument(
|
|
49
49
|
new Source(`query QueryWithOffset { hero { nam } }`, "testDocument", {
|
|
50
50
|
line: 5,
|
|
51
51
|
column: 10,
|
|
52
|
-
})
|
|
52
|
+
}),
|
|
53
53
|
);
|
|
54
54
|
describe("Language server diagnostics", () => {
|
|
55
55
|
describe("#collectExecutableDefinitionDiagnositics", () => {
|
|
56
56
|
it("returns no diagnostics for a correct document", () => {
|
|
57
57
|
const diagnostics = collectExecutableDefinitionDiagnositics(
|
|
58
58
|
schema,
|
|
59
|
-
validDocument
|
|
59
|
+
validDocument,
|
|
60
60
|
);
|
|
61
61
|
expect(diagnostics.length).toEqual(0);
|
|
62
62
|
});
|
|
63
63
|
it("returns two diagnostics for a document with two errors", () => {
|
|
64
64
|
const diagnostics = collectExecutableDefinitionDiagnositics(
|
|
65
65
|
schema,
|
|
66
|
-
invalidDocument
|
|
66
|
+
invalidDocument,
|
|
67
67
|
);
|
|
68
68
|
expect(diagnostics.length).toEqual(2);
|
|
69
69
|
});
|
|
70
70
|
it("returns no diagnostics for a document that includes type definitions", () => {
|
|
71
71
|
const diagnostics = collectExecutableDefinitionDiagnositics(
|
|
72
72
|
schema,
|
|
73
|
-
documentWithTypes
|
|
73
|
+
documentWithTypes,
|
|
74
74
|
);
|
|
75
75
|
expect(diagnostics.length).toEqual(0);
|
|
76
76
|
});
|
|
77
77
|
it("correctly offsets locations", () => {
|
|
78
78
|
const diagnostics = collectExecutableDefinitionDiagnositics(
|
|
79
79
|
schema,
|
|
80
|
-
documentWithOffset
|
|
80
|
+
documentWithOffset,
|
|
81
81
|
);
|
|
82
82
|
expect(diagnostics.length).toEqual(1);
|
|
83
83
|
expect(diagnostics[0].range.start.character).toEqual(40);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IntrospectionQuery } from "graphql";
|
|
2
2
|
|
|
3
|
-
export const starwarsSchema
|
|
3
|
+
export const starwarsSchema = {
|
|
4
4
|
__schema: {
|
|
5
5
|
queryType: {
|
|
6
6
|
name: "Query",
|
|
@@ -1914,4 +1914,4 @@ export const starwarsSchema: IntrospectionQuery = {
|
|
|
1914
1914
|
},
|
|
1915
1915
|
],
|
|
1916
1916
|
},
|
|
1917
|
-
};
|
|
1917
|
+
} as IntrospectionQuery;
|
|
@@ -1,128 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import URI from "vscode-uri";
|
|
1
|
+
import { ClientConfig, parseApolloConfig } from "../";
|
|
2
|
+
import { URI } from "vscode-uri";
|
|
3
3
|
|
|
4
4
|
describe("ApolloConfig", () => {
|
|
5
5
|
describe("confifDirURI", () => {
|
|
6
6
|
it("properly parses dir paths for configDirURI", () => {
|
|
7
7
|
const uri = URI.parse("/test/dir/name");
|
|
8
|
-
const config =
|
|
9
|
-
{ service: { name: "hai", ...DefaultConfigBase } },
|
|
10
|
-
uri
|
|
11
|
-
);
|
|
8
|
+
const config = parseApolloConfig({ client: { service: "hai" } }, uri);
|
|
12
9
|
// can be either /test/dir/name or \\test\\dir\\name depending on platform
|
|
13
10
|
// this difference is fine :)
|
|
14
|
-
expect(config
|
|
15
|
-
/\/test\/dir\/name|\\test\\dir\\name
|
|
11
|
+
expect(config?.configDirURI?.fsPath).toMatch(
|
|
12
|
+
/\/test\/dir\/name|\\test\\dir\\name/,
|
|
16
13
|
);
|
|
17
14
|
});
|
|
18
15
|
it("properly parses filepaths for configDirURI", () => {
|
|
19
16
|
const uri = URI.parse("/test/dir/name/apollo.config.js");
|
|
20
|
-
const config =
|
|
21
|
-
{
|
|
22
|
-
|
|
17
|
+
const config = parseApolloConfig(
|
|
18
|
+
{
|
|
19
|
+
client: { service: "hai" },
|
|
20
|
+
},
|
|
21
|
+
uri,
|
|
23
22
|
);
|
|
24
23
|
// can be either /test/dir/name or \\test\\dir\\name depending on platform
|
|
25
24
|
// this difference is fine :)
|
|
26
|
-
expect(config
|
|
27
|
-
/\/test\/dir\/name|\\test\\dir\\name
|
|
25
|
+
expect(config?.configDirURI?.fsPath).toMatch(
|
|
26
|
+
/\/test\/dir\/name|\\test\\dir\\name/,
|
|
28
27
|
);
|
|
29
28
|
});
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
describe("projects", () => {
|
|
33
|
-
it("creates a ClientConfig when client is present", () => {
|
|
34
|
-
const rawConfig: ApolloConfigFormat = {
|
|
35
|
-
client: { service: "my-service", ...DefaultConfigBase },
|
|
36
|
-
};
|
|
37
|
-
const config = new ApolloConfig(rawConfig);
|
|
38
|
-
const projects = config.projects;
|
|
39
|
-
expect(projects).toHaveLength(1);
|
|
40
|
-
expect(projects[0].isClient).toBeTruthy();
|
|
41
|
-
});
|
|
42
|
-
it("creates a ServiceConfig when service is present", () => {
|
|
43
|
-
const rawConfig: ApolloConfigFormat = {
|
|
44
|
-
service: { name: "my-service", ...DefaultConfigBase },
|
|
45
|
-
};
|
|
46
|
-
const config = new ApolloConfig(rawConfig);
|
|
47
|
-
const projects = config.projects;
|
|
48
|
-
|
|
49
|
-
expect(projects).toHaveLength(1);
|
|
50
|
-
expect(projects[0].isService).toBeTruthy();
|
|
51
|
-
});
|
|
52
|
-
it("creates multiple configs when both client and service are present", () => {
|
|
53
|
-
const rawConfig: ApolloConfigFormat = {
|
|
54
|
-
client: { service: "my-service", ...DefaultConfigBase },
|
|
55
|
-
service: { name: "my-service", ...DefaultConfigBase },
|
|
56
|
-
};
|
|
57
|
-
const config = new ApolloConfig(rawConfig);
|
|
58
|
-
const projects = config.projects;
|
|
59
|
-
|
|
60
|
-
expect(projects).toHaveLength(2);
|
|
61
|
-
expect(projects.find((c) => c.isClient)).toBeTruthy();
|
|
62
|
-
expect(projects.find((c) => c.isService)).toBeTruthy();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
31
|
describe("variant", () => {
|
|
67
32
|
it("gets default variant when none is set", () => {
|
|
68
|
-
const config =
|
|
69
|
-
client: { service: "hai"
|
|
33
|
+
const config = parseApolloConfig({
|
|
34
|
+
client: { service: "hai" },
|
|
70
35
|
});
|
|
71
|
-
expect(config
|
|
36
|
+
expect(config?.variant).toEqual("current");
|
|
72
37
|
});
|
|
73
38
|
|
|
74
39
|
it("gets variant from service specifier", () => {
|
|
75
|
-
const config =
|
|
76
|
-
client: { service: "hai@master"
|
|
40
|
+
const config = parseApolloConfig({
|
|
41
|
+
client: { service: "hai@master" },
|
|
77
42
|
});
|
|
78
|
-
expect(config
|
|
43
|
+
expect(config?.variant).toEqual("master");
|
|
79
44
|
});
|
|
80
45
|
|
|
81
46
|
it("can set and override variants", () => {
|
|
82
|
-
const config =
|
|
83
|
-
client: { service: "hai@master"
|
|
47
|
+
const config = parseApolloConfig({
|
|
48
|
+
client: { service: "hai@master" },
|
|
84
49
|
});
|
|
85
|
-
config
|
|
86
|
-
expect(config
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe("setDefaults", () => {
|
|
91
|
-
it("can override engine defaults", () => {
|
|
92
|
-
const config = new ApolloConfig({ client: { ...DefaultConfigBase } });
|
|
93
|
-
const overrides = {
|
|
94
|
-
engine: {
|
|
95
|
-
endpoint: "https://test.apollographql.com/api/graphql",
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
config.setDefaults(overrides);
|
|
99
|
-
expect(config.engine).toEqual(overrides.engine);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("can override client defaults", () => {
|
|
103
|
-
const config = new ApolloConfig({ client: { ...DefaultConfigBase } });
|
|
104
|
-
const overrides = {
|
|
105
|
-
client: {
|
|
106
|
-
name: "my-client",
|
|
107
|
-
service: "my-service@master",
|
|
108
|
-
...DefaultConfigBase,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
config.setDefaults(overrides);
|
|
112
|
-
expect(config.client).toEqual(overrides.client);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("can override service defaults", () => {
|
|
116
|
-
const config = new ApolloConfig({ client: { ...DefaultConfigBase } });
|
|
117
|
-
const overrides = {
|
|
118
|
-
service: {
|
|
119
|
-
name: "my-service",
|
|
120
|
-
url: "localhost:9090",
|
|
121
|
-
...DefaultConfigBase,
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
config.setDefaults(overrides);
|
|
125
|
-
expect(config.service).toEqual(config.service);
|
|
50
|
+
config!.variant = "new";
|
|
51
|
+
expect(config?.variant).toEqual("new");
|
|
126
52
|
});
|
|
127
53
|
});
|
|
128
54
|
});
|