vscode-apollo 2.3.0 → 2.3.1
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 +0 -16
- package/.github/workflows/E2E.yml +27 -0
- package/CHANGELOG.md +6 -0
- package/README.md +6 -3
- package/jest.e2e.config.js +2 -0
- package/package.json +2 -1
- package/sampleWorkspace/localSchemaArray/src/test.js +1 -1
- package/sampleWorkspace/rover/apollo.config.yaml +2 -1
- package/sampleWorkspace/rover/src/test.graphql +11 -10
- package/src/__e2e__/runTests.js +1 -0
- package/src/language-server/__e2e__/clientSchema.e2e.ts +58 -28
- package/src/language-server/__e2e__/httpSchema.e2e.ts +23 -4
- package/src/language-server/__e2e__/localSchema.e2e.ts +23 -4
- package/src/language-server/__e2e__/localSchemaArray.e2e.ts +31 -6
- package/src/language-server/__e2e__/rover.e2e.ts +150 -0
- package/src/language-server/__e2e__/studioGraph.e2e.ts +18 -6
- package/src/language-server/__e2e__/utils.ts +114 -13
package/.circleci/config.yml
CHANGED
|
@@ -53,28 +53,12 @@ jobs:
|
|
|
53
53
|
name: Test
|
|
54
54
|
command: npm run test -- --runInBand
|
|
55
55
|
|
|
56
|
-
E2E tests:
|
|
57
|
-
executor: node
|
|
58
|
-
steps:
|
|
59
|
-
- checkout
|
|
60
|
-
- npm-install
|
|
61
|
-
- run: sudo apt update && sudo apt install -y libasound2 libgbm1 libgtk-3-0 libnss3 xvfb
|
|
62
|
-
- run:
|
|
63
|
-
name: Build
|
|
64
|
-
command: npm run build:production
|
|
65
|
-
- run:
|
|
66
|
-
command: echo 'APOLLO_KEY="service:bob-123:489fhseo4"' > ./sampleWorkspace/spotifyGraph/.env
|
|
67
|
-
- run:
|
|
68
|
-
name: E2E tests
|
|
69
|
-
command: xvfb-run -a npm run test:extension
|
|
70
|
-
|
|
71
56
|
workflows:
|
|
72
57
|
build-test-deploy:
|
|
73
58
|
jobs:
|
|
74
59
|
- lint
|
|
75
60
|
- typescript
|
|
76
61
|
- test
|
|
77
|
-
- E2E tests
|
|
78
62
|
security-scans:
|
|
79
63
|
jobs:
|
|
80
64
|
- secops/gitleaks:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Run E2E tests
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
name: Run E2E tests
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- run: sudo apt update && sudo apt install -y libasound2 libgbm1 libgtk-3-0 libnss3 xvfb expect
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
cache: "npm"
|
|
17
|
+
- run: npm install
|
|
18
|
+
- run: npm run build:production
|
|
19
|
+
- run: echo 'APOLLO_KEY="service:bob-123:489fhseo4"' > ./sampleWorkspace/spotifyGraph/.env
|
|
20
|
+
- run: |
|
|
21
|
+
expect <<EOF
|
|
22
|
+
spawn ./node_modules/.bin/rover config auth --profile VSCode-E2E
|
|
23
|
+
expect "Copy the key and paste it into the prompt below."
|
|
24
|
+
send -- "test\n"
|
|
25
|
+
expect eof
|
|
26
|
+
EOF
|
|
27
|
+
- run: xvfb-run -a npm run test:extension
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 2.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`7a55e46b`](https://github.com/apollographql/vscode-graphql/commit/7a55e46bbce01af851a5daafd2507a7b353ea081) Thanks [@phryneas](https://github.com/phryneas)! - Fix styling in README.
|
|
8
|
+
|
|
3
9
|
## 2.3.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -33,9 +33,10 @@ Alternatively, you can create a `yaml`, `cjs`, `mjs`, or `ts` file with the same
|
|
|
33
33
|
|
|
34
34
|
For the contents of this configuration file, select one of these options:
|
|
35
35
|
|
|
36
|
+
<h3>Configure extension for schemas published to Apollo GraphOS</h3>
|
|
36
37
|
<details>
|
|
37
38
|
<summary>
|
|
38
|
-
<
|
|
39
|
+
<i>Expand for instructions.</i>
|
|
39
40
|
</summary>
|
|
40
41
|
|
|
41
42
|
To get all the benefits of the VS Code experience, it's best to link the schema that is being developed against before installing the extension. The best way to do that is by [publishing a schema](https://www.apollographql.com/docs/graphos/delivery/publishing-schemas/) to the Apollo schema registry.
|
|
@@ -70,9 +71,10 @@ After this is done, VS Code can be reloaded and the Apollo integration will conn
|
|
|
70
71
|
|
|
71
72
|
</details>
|
|
72
73
|
|
|
74
|
+
<h3 id="local-schemas">Configure extension to use introspection from a locally running service</h3>
|
|
73
75
|
<details>
|
|
74
76
|
<summary>
|
|
75
|
-
<
|
|
77
|
+
<i>Expand for instructions.</i>
|
|
76
78
|
</summary>
|
|
77
79
|
|
|
78
80
|
Sometimes it may make sense to link the editor to a locally running version of a schema to try out new designs that are in active development. To do this, the `apollo.config.json` file can be linked to a local service definition:
|
|
@@ -92,9 +94,10 @@ Linking to the local schema won't provide all features, such as switching graph
|
|
|
92
94
|
|
|
93
95
|
</details>
|
|
94
96
|
|
|
97
|
+
<h3 id="local-schema-files">Configure extension for local schema files</h3>
|
|
95
98
|
<details>
|
|
96
99
|
<summary>
|
|
97
|
-
<
|
|
100
|
+
<i>Expand for instructions.</i>
|
|
98
101
|
</summary>
|
|
99
102
|
|
|
100
103
|
You might not always have a running server to link to, so the extension also supports linking to a local schema file.
|
package/jest.e2e.config.js
CHANGED
|
@@ -3,6 +3,8 @@ const path = require("path");
|
|
|
3
3
|
|
|
4
4
|
module.exports = {
|
|
5
5
|
moduleFileExtensions: ["js", "ts"],
|
|
6
|
+
// restrict the roots here so jest doesn't complain about *other* snapshots it sees as obsolete
|
|
7
|
+
roots: ["<rootDir>/src/language-server/__e2e__"],
|
|
6
8
|
testMatch: ["<rootDir>/src/**/*.e2e.ts"],
|
|
7
9
|
testEnvironment: "./src/__e2e__/vscode-environment.js",
|
|
8
10
|
setupFiles: ["./src/__e2e__/setup.js"],
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "vscode-apollo",
|
|
3
3
|
"displayName": "Apollo GraphQL",
|
|
4
4
|
"description": "Rich editor support for GraphQL client and server development that seamlessly integrates with the Apollo platform",
|
|
5
|
-
"version": "2.3.
|
|
5
|
+
"version": "2.3.1",
|
|
6
6
|
"referenceID": "87197759-7617-40d0-b32e-46d378e907c7",
|
|
7
7
|
"author": "Apollo GraphQL <opensource@apollographql.com>",
|
|
8
8
|
"license": "MIT",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"zod-validation-error": "3.3.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
+
"@apollo/rover": "0.27.0-alpha.0",
|
|
68
69
|
"@changesets/changelog-github": "0.4.8",
|
|
69
70
|
"@changesets/cli": "2.26.2",
|
|
70
71
|
"@graphql-codegen/cli": "^5.0.2",
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
""
|
|
1
|
+
extend schema
|
|
2
|
+
@link(
|
|
3
|
+
url: "https://specs.apollo.dev/federation/v2.8"
|
|
4
|
+
import: ["@key", "@override", "@requires", "@external", "@shareable"]
|
|
5
|
+
)
|
|
6
|
+
|
|
4
7
|
type Query {
|
|
5
|
-
|
|
8
|
+
a: A
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
""
|
|
9
|
-
|
|
10
|
-
""
|
|
11
|
-
|
|
12
|
-
id: ID!
|
|
13
|
-
name: String!
|
|
11
|
+
type A @key(fields: "a") {
|
|
12
|
+
a: ID @override(from: "DNE")
|
|
13
|
+
b: String! @requires(fields: "c") @shareable
|
|
14
|
+
c: String! @external
|
|
14
15
|
}
|
package/src/__e2e__/runTests.js
CHANGED
|
@@ -18,6 +18,7 @@ async function main() {
|
|
|
18
18
|
const TEST_PORT = 7096;
|
|
19
19
|
process.env.APOLLO_ENGINE_ENDPOINT = "http://localhost:7096/apollo";
|
|
20
20
|
process.env.MOCK_SERVER_PORT = String(TEST_PORT);
|
|
21
|
+
process.env.APOLLO_FEATURE_FLAGS = "rover";
|
|
21
22
|
disposables.push(
|
|
22
23
|
...(await Promise.all([
|
|
23
24
|
runMockServer(TEST_PORT, false, loadDefaultMocks),
|
|
@@ -1,24 +1,45 @@
|
|
|
1
1
|
import { TextEditor } from "vscode";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
closeAllEditors,
|
|
4
|
+
openEditor,
|
|
5
|
+
getCompletionItems,
|
|
6
|
+
getHover,
|
|
7
|
+
getPositionForEditor,
|
|
8
|
+
GetPositionFn,
|
|
9
|
+
} from "./utils";
|
|
3
10
|
|
|
4
11
|
let editor: TextEditor;
|
|
12
|
+
let getPosition: GetPositionFn;
|
|
5
13
|
beforeAll(async () => {
|
|
6
14
|
closeAllEditors();
|
|
7
15
|
editor = await openEditor("clientSchema/src/test.js");
|
|
16
|
+
getPosition = getPositionForEditor(editor);
|
|
8
17
|
});
|
|
9
18
|
|
|
10
19
|
test("completion", async () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
expect(
|
|
21
|
+
(await getCompletionItems(editor, getPosition("dro|id")))[0],
|
|
22
|
+
).toStrictEqual({
|
|
23
|
+
label: "droid",
|
|
24
|
+
detail: "Droid",
|
|
25
|
+
});
|
|
26
|
+
expect(
|
|
27
|
+
(await getCompletionItems(editor, getPosition("na|me")))[0],
|
|
28
|
+
).toStrictEqual({
|
|
29
|
+
label: "name",
|
|
30
|
+
detail: "String!",
|
|
31
|
+
});
|
|
32
|
+
expect(
|
|
33
|
+
(await getCompletionItems(editor, getPosition("mo|del")))[0],
|
|
34
|
+
).toStrictEqual({
|
|
35
|
+
label: "model",
|
|
36
|
+
detail: "String",
|
|
37
|
+
});
|
|
17
38
|
});
|
|
18
39
|
|
|
19
40
|
test("hover", async () => {
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
expect(await getHover(editor, getPosition("featu|reFlagDefer")))
|
|
42
|
+
.toMatchInlineSnapshot(`
|
|
22
43
|
"\`\`\`graphql
|
|
23
44
|
Query.featureFlagDefer: Boolean!
|
|
24
45
|
\`\`\`
|
|
@@ -32,8 +53,8 @@ Query.featureFlagDefer: Boolean!
|
|
|
32
53
|
Whether to use defer"
|
|
33
54
|
`);
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
expect(await getHover(editor, getPosition("@c|lient(always: false)")))
|
|
57
|
+
.toMatchInlineSnapshot(`
|
|
37
58
|
"\`\`\`graphql
|
|
38
59
|
@client(always: Boolean)
|
|
39
60
|
\`\`\`
|
|
@@ -43,8 +64,8 @@ Whether to use defer"
|
|
|
43
64
|
Direct the client to resolve this field locally, either from the cache or local resolvers."
|
|
44
65
|
`);
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
|
|
67
|
+
expect(await getHover(editor, getPosition("@client(alwa|ys: false)")))
|
|
68
|
+
.toMatchInlineSnapshot(`
|
|
48
69
|
"\`\`\`graphql
|
|
49
70
|
always: Boolean
|
|
50
71
|
\`\`\`
|
|
@@ -55,8 +76,8 @@ When true, the client will never use the cache for this value. See
|
|
|
55
76
|
https://www.apollographql.com/docs/react/local-state/local-resolvers/#forcing-resolvers-with-clientalways-true"
|
|
56
77
|
`);
|
|
57
78
|
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
expect(await getHover(editor, getPosition('@expo|rt(as: "defer")')))
|
|
80
|
+
.toMatchInlineSnapshot(`
|
|
60
81
|
"\`\`\`graphql
|
|
61
82
|
@export(as: String!)
|
|
62
83
|
\`\`\`
|
|
@@ -66,7 +87,9 @@ https://www.apollographql.com/docs/react/local-state/local-resolvers/#forcing-re
|
|
|
66
87
|
Export this locally resolved field as a variable to be used in the remainder of this query. See
|
|
67
88
|
https://www.apollographql.com/docs/react/local-state/local-resolvers/#using-client-fields-as-variables"
|
|
68
89
|
`);
|
|
69
|
-
|
|
90
|
+
|
|
91
|
+
expect(await getHover(editor, getPosition('@export(a|s: "defer")')))
|
|
92
|
+
.toMatchInlineSnapshot(`
|
|
70
93
|
"\`\`\`graphql
|
|
71
94
|
as: String!
|
|
72
95
|
\`\`\`
|
|
@@ -76,8 +99,8 @@ as: String!
|
|
|
76
99
|
The variable name to export this field as."
|
|
77
100
|
`);
|
|
78
101
|
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
expect(await getHover(editor, getPosition("@nonre|active")))
|
|
103
|
+
.toMatchInlineSnapshot(`
|
|
81
104
|
"\`\`\`graphql
|
|
82
105
|
@nonreactive
|
|
83
106
|
\`\`\`
|
|
@@ -89,8 +112,9 @@ This allows parent components to fetch data to be rendered by their children wit
|
|
|
89
112
|
https://www.apollographql.com/docs/react/data/directives#nonreactive"
|
|
90
113
|
`);
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
expect(
|
|
116
|
+
await getHover(editor, getPosition('@def|er(if: $defer, label: "fc")')),
|
|
117
|
+
).toMatchInlineSnapshot(`
|
|
94
118
|
"\`\`\`graphql
|
|
95
119
|
@defer(if: Boolean, label: String)
|
|
96
120
|
\`\`\`
|
|
@@ -101,8 +125,10 @@ This directive enables your queries to receive data for specific fields incremen
|
|
|
101
125
|
This is helpful whenever some fields in a query take much longer to resolve than others.
|
|
102
126
|
https://www.apollographql.com/docs/react/data/directives#defer"
|
|
103
127
|
`);
|
|
104
|
-
|
|
105
|
-
expect(
|
|
128
|
+
|
|
129
|
+
expect(
|
|
130
|
+
await getHover(editor, getPosition('@defer(i|f: $defer, label: "fc")')),
|
|
131
|
+
).toMatchInlineSnapshot(`
|
|
106
132
|
"\`\`\`graphql
|
|
107
133
|
if: Boolean
|
|
108
134
|
\`\`\`
|
|
@@ -111,8 +137,10 @@ if: Boolean
|
|
|
111
137
|
|
|
112
138
|
When true fragment may be deferred, if omitted defaults to true."
|
|
113
139
|
`);
|
|
114
|
-
|
|
115
|
-
expect(
|
|
140
|
+
|
|
141
|
+
expect(
|
|
142
|
+
await getHover(editor, getPosition('@defer(if: $defer, labe|l: "fc")')),
|
|
143
|
+
).toMatchInlineSnapshot(`
|
|
116
144
|
"\`\`\`graphql
|
|
117
145
|
label: String
|
|
118
146
|
\`\`\`
|
|
@@ -123,8 +151,9 @@ A unique label across all @defer and @stream directives in an operation.
|
|
|
123
151
|
This label should be used by GraphQL clients to identify the data from patch responses and associate it with the correct fragment.
|
|
124
152
|
If provided, the GraphQL Server must add it to the payload."
|
|
125
153
|
`);
|
|
126
|
-
|
|
127
|
-
expect(await getHover(editor,
|
|
154
|
+
|
|
155
|
+
expect(await getHover(editor, getPosition('@connec|tion(key: "feed")')))
|
|
156
|
+
.toMatchInlineSnapshot(`
|
|
128
157
|
"\`\`\`graphql
|
|
129
158
|
@connection(key: String!, filter: [String!])
|
|
130
159
|
\`\`\`
|
|
@@ -134,8 +163,9 @@ If provided, the GraphQL Server must add it to the payload."
|
|
|
134
163
|
Specify a custom store key for this result. See
|
|
135
164
|
https://www.apollographql.com/docs/react/caching/advanced-topics/#the-connection-directive"
|
|
136
165
|
`);
|
|
137
|
-
|
|
138
|
-
expect(await getHover(editor,
|
|
166
|
+
|
|
167
|
+
expect(await getHover(editor, getPosition('@connection(ke|y: "feed")')))
|
|
168
|
+
.toMatchInlineSnapshot(`
|
|
139
169
|
"\`\`\`graphql
|
|
140
170
|
key: String!
|
|
141
171
|
\`\`\`
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { TextEditor } from "vscode";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
closeAllEditors,
|
|
4
|
+
openEditor,
|
|
5
|
+
getCompletionItems,
|
|
6
|
+
getHover,
|
|
7
|
+
getPositionForEditor,
|
|
8
|
+
GetPositionFn,
|
|
9
|
+
} from "./utils";
|
|
3
10
|
|
|
4
11
|
let editor: TextEditor;
|
|
12
|
+
let getPosition: GetPositionFn;
|
|
5
13
|
beforeAll(async () => {
|
|
6
14
|
closeAllEditors();
|
|
7
15
|
editor = await openEditor("httpSchema/src/test.js");
|
|
16
|
+
getPosition = getPositionForEditor(editor);
|
|
8
17
|
});
|
|
9
18
|
|
|
10
19
|
test("completion", async () => {
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
expect(
|
|
21
|
+
(await getCompletionItems(editor, getPosition("bo|oks")))[0],
|
|
22
|
+
).toStrictEqual({
|
|
23
|
+
label: "books",
|
|
24
|
+
detail: "[Book]",
|
|
25
|
+
});
|
|
26
|
+
expect(
|
|
27
|
+
(await getCompletionItems(editor, getPosition("au|thor")))[0],
|
|
28
|
+
).toStrictEqual({
|
|
29
|
+
label: "author",
|
|
30
|
+
detail: "String",
|
|
31
|
+
});
|
|
13
32
|
});
|
|
14
33
|
|
|
15
34
|
test("hover", async () => {
|
|
16
|
-
expect(await getHover(editor,
|
|
35
|
+
expect(await getHover(editor, getPosition("au|thor"))).toMatchInlineSnapshot(`
|
|
17
36
|
"\`\`\`graphql
|
|
18
37
|
Book.author: String
|
|
19
38
|
\`\`\`"
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { TextEditor } from "vscode";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
closeAllEditors,
|
|
4
|
+
openEditor,
|
|
5
|
+
getCompletionItems,
|
|
6
|
+
getHover,
|
|
7
|
+
GetPositionFn,
|
|
8
|
+
getPositionForEditor,
|
|
9
|
+
} from "./utils";
|
|
3
10
|
|
|
4
11
|
let editor: TextEditor;
|
|
12
|
+
let getPosition: GetPositionFn;
|
|
5
13
|
beforeAll(async () => {
|
|
6
14
|
closeAllEditors();
|
|
7
15
|
editor = await openEditor("localSchema/src/test.js");
|
|
16
|
+
getPosition = getPositionForEditor(editor);
|
|
8
17
|
});
|
|
9
18
|
|
|
10
19
|
test("completion", async () => {
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
expect(
|
|
21
|
+
(await getCompletionItems(editor, getPosition("dro|id")))[0],
|
|
22
|
+
).toStrictEqual({
|
|
23
|
+
label: "droid",
|
|
24
|
+
detail: "Droid",
|
|
25
|
+
});
|
|
26
|
+
expect(
|
|
27
|
+
(await getCompletionItems(editor, getPosition("na|me")))[0],
|
|
28
|
+
).toStrictEqual({
|
|
29
|
+
label: "name",
|
|
30
|
+
detail: "String!",
|
|
31
|
+
});
|
|
13
32
|
});
|
|
14
33
|
|
|
15
34
|
test("hover", async () => {
|
|
16
|
-
expect(await getHover(editor,
|
|
35
|
+
expect(await getHover(editor, getPosition("na|me"))).toMatchInlineSnapshot(`
|
|
17
36
|
"\`\`\`graphql
|
|
18
37
|
Droid.name: String!
|
|
19
38
|
\`\`\`
|
|
@@ -1,20 +1,45 @@
|
|
|
1
1
|
import { TextEditor } from "vscode";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
closeAllEditors,
|
|
4
|
+
openEditor,
|
|
5
|
+
getCompletionItems,
|
|
6
|
+
getHover,
|
|
7
|
+
GetPositionFn,
|
|
8
|
+
getPositionForEditor,
|
|
9
|
+
} from "./utils";
|
|
3
10
|
|
|
4
11
|
let editor: TextEditor;
|
|
12
|
+
let getPosition: GetPositionFn;
|
|
5
13
|
beforeAll(async () => {
|
|
6
14
|
closeAllEditors();
|
|
7
15
|
editor = await openEditor("localSchemaArray/src/test.js");
|
|
16
|
+
getPosition = getPositionForEditor(editor);
|
|
8
17
|
});
|
|
9
18
|
|
|
10
19
|
test("completion", async () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
expect(
|
|
21
|
+
(await getCompletionItems(editor, getPosition("dro|id")))[0],
|
|
22
|
+
).toStrictEqual({
|
|
23
|
+
label: "droid",
|
|
24
|
+
detail: "Droid",
|
|
25
|
+
});
|
|
26
|
+
expect(
|
|
27
|
+
(await getCompletionItems(editor, getPosition("d|Name: name")))[0],
|
|
28
|
+
).toStrictEqual({
|
|
29
|
+
label: "name",
|
|
30
|
+
detail: "String!",
|
|
31
|
+
});
|
|
32
|
+
expect(
|
|
33
|
+
(await getCompletionItems(editor, getPosition("pl|anet")))[0],
|
|
34
|
+
).toStrictEqual({
|
|
35
|
+
label: "planets",
|
|
36
|
+
detail: "[Planet]",
|
|
37
|
+
});
|
|
14
38
|
});
|
|
15
39
|
|
|
16
40
|
test("hover", async () => {
|
|
17
|
-
expect(await getHover(editor,
|
|
41
|
+
expect(await getHover(editor, getPosition("d|Name: name")))
|
|
42
|
+
.toMatchInlineSnapshot(`
|
|
18
43
|
"\`\`\`graphql
|
|
19
44
|
Droid.name: String!
|
|
20
45
|
\`\`\`
|
|
@@ -23,7 +48,7 @@ Droid.name: String!
|
|
|
23
48
|
|
|
24
49
|
What others call this droid"
|
|
25
50
|
`);
|
|
26
|
-
expect(await getHover(editor,
|
|
51
|
+
expect(await getHover(editor, getPosition("pl|anet"))).toMatchInlineSnapshot(`
|
|
27
52
|
"\`\`\`graphql
|
|
28
53
|
Query.planets: [Planet]
|
|
29
54
|
\`\`\`"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { test as origTest } from "@jest/globals";
|
|
2
|
+
import { load } from "js-yaml";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { ParsedApolloConfigFormat } from "../config";
|
|
7
|
+
import { TextEditor } from "vscode";
|
|
8
|
+
import {
|
|
9
|
+
closeAllEditors,
|
|
10
|
+
openEditor,
|
|
11
|
+
getCompletionItems,
|
|
12
|
+
getHover,
|
|
13
|
+
getPositionForEditor,
|
|
14
|
+
GetPositionFn,
|
|
15
|
+
getFullSemanticTokens,
|
|
16
|
+
getDefinitions,
|
|
17
|
+
} from "./utils";
|
|
18
|
+
|
|
19
|
+
// we want to skip these tests unless the user running them has a rover config profile named "VSCode-E2E"
|
|
20
|
+
let test = origTest.skip;
|
|
21
|
+
try {
|
|
22
|
+
const roverProjectDir = join(__dirname, "../../../sampleWorkspace/rover");
|
|
23
|
+
const config = load(
|
|
24
|
+
readFileSync(join(roverProjectDir, "apollo.config.yaml"), "utf-8"),
|
|
25
|
+
) as ParsedApolloConfigFormat;
|
|
26
|
+
const roverBin = join(roverProjectDir, config.rover!.bin);
|
|
27
|
+
const result = execFileSync(roverBin, [
|
|
28
|
+
"config",
|
|
29
|
+
"list",
|
|
30
|
+
"--format=json",
|
|
31
|
+
]).toString("utf8");
|
|
32
|
+
const parsed = JSON.parse(result);
|
|
33
|
+
if (parsed.data.profiles.includes("VSCode-E2E")) {
|
|
34
|
+
test = origTest;
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {}
|
|
37
|
+
if (test === origTest.skip) {
|
|
38
|
+
console.info(
|
|
39
|
+
"Skipping rover E2E tests: no profile with the name 'VSCode-E2E'\n" +
|
|
40
|
+
"You can create one by running `rover config auth --profile VSCode-E2E`",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let editor: TextEditor;
|
|
45
|
+
let getPosition: GetPositionFn;
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
closeAllEditors();
|
|
48
|
+
editor = await openEditor("rover/src/test.graphql");
|
|
49
|
+
getPosition = getPositionForEditor(editor);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("hover", async () => {
|
|
53
|
+
expect(await getHover(editor, getPosition("@over|ride(from")))
|
|
54
|
+
.toMatchInlineSnapshot(`
|
|
55
|
+
"The [\`@override\`](https://www.apollographql.com/docs/federation/federated-schemas/federated-directives/#override) directive indicates that an object field is now resolved by this subgraph instead of another subgraph where it's also defined. This enables you to migrate a field from one subgraph to another.
|
|
56
|
+
|
|
57
|
+
You can apply \`@override\` to entity fields and fields of the root operation types (such as \`Query\` and \`Mutation\`). A second \`label\` argument can be used to progressively override a field. See [the docs](https://www.apollographql.com/docs/federation/entities/migrate-fields/#incremental-migration-with-progressive-override) for more information.
|
|
58
|
+
***
|
|
59
|
+
\`\`\`graphql
|
|
60
|
+
directive @override(from: String!, label: String) on FIELD_DEFINITION
|
|
61
|
+
\`\`\`"
|
|
62
|
+
`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("completion", async () => {
|
|
66
|
+
expect(await getCompletionItems(editor, getPosition("@over|ride(from")))
|
|
67
|
+
.toMatchInlineSnapshot(`
|
|
68
|
+
[
|
|
69
|
+
{
|
|
70
|
+
"detail": undefined,
|
|
71
|
+
"label": "@deprecated",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"detail": undefined,
|
|
75
|
+
"label": "@external",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"detail": undefined,
|
|
79
|
+
"label": "@federation__authenticated",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"detail": undefined,
|
|
83
|
+
"label": "@federation__inaccessible",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"detail": undefined,
|
|
87
|
+
"label": "@federation__policy(…)",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"detail": undefined,
|
|
91
|
+
"label": "@federation__provides(…)",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"detail": undefined,
|
|
95
|
+
"label": "@federation__requiresScopes(…)",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"detail": undefined,
|
|
99
|
+
"label": "@federation__tag(…)",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"detail": undefined,
|
|
103
|
+
"label": "@override(…)",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"detail": undefined,
|
|
107
|
+
"label": "@requires(…)",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"detail": undefined,
|
|
111
|
+
"label": "@shareable",
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
`);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("semantic tokens", async () => {
|
|
118
|
+
const tokens = await getFullSemanticTokens(editor);
|
|
119
|
+
expect(tokens[0]).toStrictEqual({
|
|
120
|
+
startPosition: getPosition('fields: "|a"'),
|
|
121
|
+
endPosition: getPosition('fields: "a|"'),
|
|
122
|
+
tokenType: "property",
|
|
123
|
+
tokenModifiers: [],
|
|
124
|
+
});
|
|
125
|
+
expect(tokens[1]).toStrictEqual({
|
|
126
|
+
startPosition: getPosition('fields: "|c"'),
|
|
127
|
+
endPosition: getPosition('fields: "c|"'),
|
|
128
|
+
tokenType: "property",
|
|
129
|
+
tokenModifiers: [],
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("definitions", async () => {
|
|
134
|
+
const definitions = await getDefinitions(editor, getPosition("a: |A"));
|
|
135
|
+
|
|
136
|
+
expect(definitions[0].targetUri.toString()).toBe(
|
|
137
|
+
editor.document.uri.toString(),
|
|
138
|
+
);
|
|
139
|
+
expect(
|
|
140
|
+
editor.document.getText(definitions[0].targetSelectionRange!),
|
|
141
|
+
).toMatchInlineSnapshot(`"A"`);
|
|
142
|
+
expect(editor.document.getText(definitions[0].targetRange))
|
|
143
|
+
.toMatchInlineSnapshot(`
|
|
144
|
+
"type A @key(fields: "a") {
|
|
145
|
+
a: ID @override(from: "DNE")
|
|
146
|
+
b: String! @requires(fields: "c") @shareable
|
|
147
|
+
c: String! @external
|
|
148
|
+
}"
|
|
149
|
+
`);
|
|
150
|
+
});
|
|
@@ -2,15 +2,14 @@ import { TextEditor } from "vscode";
|
|
|
2
2
|
import {
|
|
3
3
|
closeAllEditors,
|
|
4
4
|
openEditor,
|
|
5
|
-
|
|
5
|
+
getCompletionItems,
|
|
6
6
|
getHover,
|
|
7
7
|
getExtension,
|
|
8
8
|
getOutputChannelDocument,
|
|
9
9
|
reloadService,
|
|
10
|
+
getPositionForEditor,
|
|
10
11
|
} from "./utils";
|
|
11
12
|
import mocks from "../../__e2e__/mocks.js";
|
|
12
|
-
import vscode from "vscode";
|
|
13
|
-
import { scheduler } from "node:timers/promises";
|
|
14
13
|
|
|
15
14
|
const mockPort = Number(process.env.MOCK_SERVER_PORT);
|
|
16
15
|
beforeAll(async () => {
|
|
@@ -19,13 +18,26 @@ beforeAll(async () => {
|
|
|
19
18
|
|
|
20
19
|
test("completion", async () => {
|
|
21
20
|
const editor = await openEditor("spotifyGraph/src/test.js");
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const getPosition = getPositionForEditor(editor);
|
|
22
|
+
expect(
|
|
23
|
+
(await getCompletionItems(editor, getPosition("pr|ofile")))[0],
|
|
24
|
+
).toStrictEqual({
|
|
25
|
+
label: "profile",
|
|
26
|
+
detail: "CurrentUserProfile!",
|
|
27
|
+
});
|
|
28
|
+
expect(
|
|
29
|
+
(await getCompletionItems(editor, getPosition("dis|playName")))[0],
|
|
30
|
+
).toStrictEqual({
|
|
31
|
+
label: "displayName",
|
|
32
|
+
detail: "String",
|
|
33
|
+
});
|
|
24
34
|
});
|
|
25
35
|
|
|
26
36
|
test("hover", async () => {
|
|
27
37
|
const editor = await openEditor("spotifyGraph/src/test.js");
|
|
28
|
-
|
|
38
|
+
const getPosition = getPositionForEditor(editor);
|
|
39
|
+
expect(await getHover(editor, getPosition("pr|ofile")))
|
|
40
|
+
.toMatchInlineSnapshot(`
|
|
29
41
|
"\`\`\`graphql
|
|
30
42
|
CurrentUser.profile: CurrentUserProfile!
|
|
31
43
|
\`\`\`
|
|
@@ -8,6 +8,25 @@ function resolve(file: string) {
|
|
|
8
8
|
return join(__dirname, "..", "..", "..", "sampleWorkspace", file);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export type GetPositionFn = ReturnType<typeof getPositionForEditor>;
|
|
12
|
+
export function getPositionForEditor(editor: vscode.TextEditor) {
|
|
13
|
+
return function getPosition(cursor: `${string}|${string}`) {
|
|
14
|
+
if (cursor.indexOf("|") !== cursor.lastIndexOf("|")) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"`getPosition` cursor description can only contain one |",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const text = editor.document.getText();
|
|
20
|
+
const idx = text.indexOf(cursor.replace("|", ""));
|
|
21
|
+
if (idx !== text.lastIndexOf(cursor.replace("|", ""))) {
|
|
22
|
+
throw new Error("`getPosition` cursor description is not unique");
|
|
23
|
+
}
|
|
24
|
+
const cursorIndex = idx + cursor.indexOf("|");
|
|
25
|
+
const position = editor.document.positionAt(cursorIndex);
|
|
26
|
+
return position;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
11
30
|
export async function closeAllEditors() {
|
|
12
31
|
while (vscode.window.visibleTextEditors.length > 0) {
|
|
13
32
|
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
|
|
@@ -50,23 +69,28 @@ export function waitForLSP(file: string) {
|
|
|
50
69
|
});
|
|
51
70
|
}
|
|
52
71
|
|
|
53
|
-
export async function
|
|
72
|
+
export async function getCompletionItems(
|
|
54
73
|
editor: vscode.TextEditor,
|
|
55
|
-
|
|
56
|
-
expected: Array<[label: string, detail: string]>,
|
|
74
|
+
position: vscode.Position,
|
|
57
75
|
) {
|
|
76
|
+
let result: { label: string; detail: string | undefined }[] | undefined = [];
|
|
58
77
|
await waitFor(async () => {
|
|
59
|
-
editor.selection = new vscode.Selection(
|
|
78
|
+
editor.selection = new vscode.Selection(
|
|
79
|
+
position.line,
|
|
80
|
+
position.character,
|
|
81
|
+
position.line,
|
|
82
|
+
position.character,
|
|
83
|
+
);
|
|
60
84
|
// without this, the completion list is not updated
|
|
61
85
|
await scheduler.wait(300);
|
|
62
86
|
const completions =
|
|
63
87
|
await vscode.commands.executeCommand<vscode.CompletionList>(
|
|
64
88
|
"vscode.executeCompletionItemProvider",
|
|
65
89
|
editor.document.uri,
|
|
66
|
-
|
|
90
|
+
position,
|
|
67
91
|
);
|
|
68
|
-
|
|
69
|
-
const labels = completions.items.
|
|
92
|
+
expect(completions.items).not.toHaveLength(0);
|
|
93
|
+
const labels = completions.items.map((item) =>
|
|
70
94
|
typeof item.label === "string"
|
|
71
95
|
? { label: item.label, detail: "" }
|
|
72
96
|
: {
|
|
@@ -74,23 +98,27 @@ export async function testCompletion(
|
|
|
74
98
|
detail: item.detail,
|
|
75
99
|
},
|
|
76
100
|
);
|
|
77
|
-
|
|
78
|
-
expected.map(([label, detail]) => ({ label, detail })),
|
|
79
|
-
);
|
|
101
|
+
result = labels;
|
|
80
102
|
});
|
|
103
|
+
return result;
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
export async function getHover(
|
|
84
107
|
editor: vscode.TextEditor,
|
|
85
|
-
|
|
108
|
+
position: vscode.Position,
|
|
86
109
|
) {
|
|
87
|
-
editor.selection = new vscode.Selection(
|
|
110
|
+
editor.selection = new vscode.Selection(
|
|
111
|
+
position.line,
|
|
112
|
+
position.character,
|
|
113
|
+
position.line,
|
|
114
|
+
position.character,
|
|
115
|
+
);
|
|
88
116
|
// without this, the completion list is not updated
|
|
89
117
|
await scheduler.wait(300);
|
|
90
118
|
const hovers = await vscode.commands.executeCommand<vscode.Hover[]>(
|
|
91
119
|
"vscode.executeHoverProvider",
|
|
92
120
|
editor.document.uri,
|
|
93
|
-
|
|
121
|
+
position,
|
|
94
122
|
);
|
|
95
123
|
|
|
96
124
|
const item = hovers[0];
|
|
@@ -149,3 +177,76 @@ export async function reloadService() {
|
|
|
149
177
|
await reloaded;
|
|
150
178
|
await scheduler.wait(100);
|
|
151
179
|
}
|
|
180
|
+
|
|
181
|
+
export async function getFullSemanticTokens(editor: vscode.TextEditor) {
|
|
182
|
+
const legend = await vscode.commands.executeCommand<
|
|
183
|
+
vscode.SemanticTokensLegend | undefined
|
|
184
|
+
>(
|
|
185
|
+
// https://github.com/microsoft/vscode/blob/d90ab31527203cdb15056df0dc84ab9ddcbbde40/src/vs/workbench/api/common/extHostApiCommands.ts#L220
|
|
186
|
+
"vscode.provideDocumentSemanticTokensLegend",
|
|
187
|
+
editor.document.uri,
|
|
188
|
+
);
|
|
189
|
+
expect(legend).toBeDefined();
|
|
190
|
+
const tokens = await vscode.commands.executeCommand<
|
|
191
|
+
vscode.SemanticTokens | undefined
|
|
192
|
+
>(
|
|
193
|
+
// https://github.com/microsoft/vscode/blob/d90ab31527203cdb15056df0dc84ab9ddcbbde40/src/vs/workbench/api/common/extHostApiCommands.ts#L229
|
|
194
|
+
"vscode.provideDocumentSemanticTokens",
|
|
195
|
+
editor.document.uri,
|
|
196
|
+
);
|
|
197
|
+
expect(tokens).toBeDefined();
|
|
198
|
+
|
|
199
|
+
return decodeSemanticTokens(tokens!, legend!);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function decodeSemanticTokens(
|
|
203
|
+
tokens: vscode.SemanticTokens,
|
|
204
|
+
legend: vscode.SemanticTokensLegend,
|
|
205
|
+
) {
|
|
206
|
+
const tokenArr = Array.from(tokens.data);
|
|
207
|
+
const decodedTokens: {
|
|
208
|
+
startPosition: vscode.Position;
|
|
209
|
+
endPosition: vscode.Position;
|
|
210
|
+
tokenType: string;
|
|
211
|
+
tokenModifiers: string[];
|
|
212
|
+
}[] = [];
|
|
213
|
+
let line = 0,
|
|
214
|
+
start = 0;
|
|
215
|
+
for (let pos = 0; pos < tokenArr.length; pos += 5) {
|
|
216
|
+
const [deltaLine, deltaStart, length, tokenType, tokenModifiers] =
|
|
217
|
+
tokenArr.slice(pos, pos + 5);
|
|
218
|
+
if (deltaLine) {
|
|
219
|
+
line += deltaLine;
|
|
220
|
+
start = 0;
|
|
221
|
+
}
|
|
222
|
+
start += deltaStart;
|
|
223
|
+
const decodedModifiers: string[] = [];
|
|
224
|
+
for (let modifiers = tokenModifiers; modifiers > 0; modifiers >>= 1) {
|
|
225
|
+
decodedModifiers.push(legend.tokenModifiers[modifiers & 0xf]);
|
|
226
|
+
}
|
|
227
|
+
const startPosition = new vscode.Position(line, start);
|
|
228
|
+
const endPosition = startPosition.translate(0, length);
|
|
229
|
+
decodedTokens.push({
|
|
230
|
+
startPosition,
|
|
231
|
+
endPosition,
|
|
232
|
+
tokenType: legend.tokenTypes[tokenType],
|
|
233
|
+
tokenModifiers: decodedModifiers,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return decodedTokens;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function getDefinitions(
|
|
240
|
+
editor: vscode.TextEditor,
|
|
241
|
+
position: vscode.Position,
|
|
242
|
+
) {
|
|
243
|
+
return vscode.commands.executeCommand<
|
|
244
|
+
// this is not the correct type, but the best match with public types I could find
|
|
245
|
+
vscode.LocationLink[]
|
|
246
|
+
>(
|
|
247
|
+
// https://github.com/microsoft/vscode/blob/d90ab31527203cdb15056df0dc84ab9ddcbbde40/src/vs/workbench/api/common/extHostApiCommands.ts#L87
|
|
248
|
+
"vscode.executeDefinitionProvider",
|
|
249
|
+
editor.document.uri,
|
|
250
|
+
position,
|
|
251
|
+
);
|
|
252
|
+
}
|