Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.

chore: add tada test suite (#241)

+30
pnpm-lock.yaml
···
specifier: ^5.3.3
version: 5.3.3
+
test/e2e/fixture-project-tada:
+
dependencies:
+
'@0no-co/graphqlsp':
+
specifier: workspace:*
+
version: link:../../../packages/graphqlsp
+
'@graphql-typed-document-node/core':
+
specifier: ^3.0.0
+
version: 3.2.0(graphql@16.8.1)
+
'@urql/core':
+
specifier: ^4.0.4
+
version: 4.2.2(graphql@16.8.1)
+
gql.tada:
+
specifier: ^1.2.1
+
version: 1.2.1(graphql@16.8.1)
+
graphql:
+
specifier: ^16.0.0
+
version: 16.8.1
+
devDependencies:
+
typescript:
+
specifier: ^5.3.3
+
version: 5.3.3
+
test/e2e/fixture-project-unused-fields:
dependencies:
'@0no-co/graphqlsp':
···
/gql.tada@1.0.0(graphql@16.8.1):
resolution: {integrity: sha512-bmZUHxXGXJNW8fPtXyWIwbLKbEjnKK3PF6XinZFK2PgcxSFJ/9p5fVDseE1xiCkf3nOA4aiSxY5OQ3xWNjCzvg==}
+
dependencies:
+
'@0no-co/graphql.web': 1.0.4(graphql@16.8.1)
+
transitivePeerDependencies:
+
- graphql
+
dev: false
+
+
/gql.tada@1.2.1(graphql@16.8.1):
+
resolution: {integrity: sha512-Nx8x3g9WLT23eu9aL/4TTFDBwm7CBGVd4F2Jp2H5oOjDpuWv12i1mTLKReQwn2V1ZP+jG8V0ATXzFQZt1pxSgw==}
dependencies:
'@0no-co/graphql.web': 1.0.4(graphql@16.8.1)
transitivePeerDependencies:
+3
test/e2e/fixture-project-tada/.vscode/settings.json
···
+
{
+
"typescript.tsdk": "node_modules/typescript/lib"
+
}
+13
test/e2e/fixture-project-tada/fixtures/fragment.ts
···
+
import { graphql } from './graphql';
+
+
// prettier-ignore
+
export const PokemonFields = graphql(`
+
fragment pokemonFields on Pokemon {
+
id
+
name
+
fleeRate
+
+
}
+
`);
+
+
export const Pokemon = () => {};
+9
test/e2e/fixture-project-tada/fixtures/graphql.ts
···
+
import { initGraphQLTada } from 'gql.tada';
+
import type { introspection } from '../introspection';
+
+
export const graphql = initGraphQLTada<{
+
introspection: introspection;
+
}>();
+
+
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
+
export { readFragment } from 'gql.tada';
+16
test/e2e/fixture-project-tada/fixtures/simple.ts
···
+
import { graphql } from './graphql';
+
import { PokemonFields } from './fragment';
+
+
// prettier-ignore
+
const x = graphql(`
+
query Pok($limit: Int!) {
+
pokemons(limit: $limit) {
+
id
+
name
+
fleeRate
+
classification
+
...pokemonFields
+
__typename
+
}
+
}
+
`, [PokemonFields]);
+13
test/e2e/fixture-project-tada/fixtures/unused-fragment.ts
···
+
import { graphql } from './graphql';
+
import { Pokemon } from './fragment';
+
+
const x = graphql(`
+
query Pok($limit: Int!) {
+
pokemons(limit: $limit) {
+
id
+
name
+
}
+
}
+
`);
+
+
console.log(Pokemon);
+9
test/e2e/fixture-project-tada/graphql.ts
···
+
import { initGraphQLTada } from 'gql.tada';
+
import type { introspection } from './introspection';
+
+
export const graphql = initGraphQLTada<{
+
introspection: introspection;
+
}>();
+
+
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
+
export { readFragment } from 'gql.tada';
+437
test/e2e/fixture-project-tada/introspection.d.ts
···
+
/* eslint-disable */
+
/* prettier-ignore */
+
+
/** An IntrospectionQuery representation of your schema.
+
*
+
* @remarks
+
* This is an introspection of your schema saved as a file by GraphQLSP.
+
* It will automatically be used by `gql.tada` to infer the types of your GraphQL documents.
+
* If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to
+
* instead save to a .ts instead of a .d.ts file.
+
*/
+
export type introspection = {
+
"__schema": {
+
"queryType": {
+
"name": "Query"
+
},
+
"mutationType": null,
+
"subscriptionType": null,
+
"types": [
+
{
+
"kind": "OBJECT",
+
"name": "Attack",
+
"fields": [
+
{
+
"name": "damage",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Int",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "name",
+
"type": {
+
"kind": "SCALAR",
+
"name": "String",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "type",
+
"type": {
+
"kind": "ENUM",
+
"name": "PokemonType",
+
"ofType": null
+
},
+
"args": []
+
}
+
],
+
"interfaces": []
+
},
+
{
+
"kind": "SCALAR",
+
"name": "Int"
+
},
+
{
+
"kind": "SCALAR",
+
"name": "String"
+
},
+
{
+
"kind": "OBJECT",
+
"name": "AttacksConnection",
+
"fields": [
+
{
+
"name": "fast",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "OBJECT",
+
"name": "Attack",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "special",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "OBJECT",
+
"name": "Attack",
+
"ofType": null
+
}
+
},
+
"args": []
+
}
+
],
+
"interfaces": []
+
},
+
{
+
"kind": "OBJECT",
+
"name": "EvolutionRequirement",
+
"fields": [
+
{
+
"name": "amount",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Int",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "name",
+
"type": {
+
"kind": "SCALAR",
+
"name": "String",
+
"ofType": null
+
},
+
"args": []
+
}
+
],
+
"interfaces": []
+
},
+
{
+
"kind": "OBJECT",
+
"name": "Pokemon",
+
"fields": [
+
{
+
"name": "attacks",
+
"type": {
+
"kind": "OBJECT",
+
"name": "AttacksConnection",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "classification",
+
"type": {
+
"kind": "SCALAR",
+
"name": "String",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "evolutionRequirements",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "OBJECT",
+
"name": "EvolutionRequirement",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "evolutions",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "OBJECT",
+
"name": "Pokemon",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "fleeRate",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Float",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "height",
+
"type": {
+
"kind": "OBJECT",
+
"name": "PokemonDimension",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "id",
+
"type": {
+
"kind": "NON_NULL",
+
"ofType": {
+
"kind": "SCALAR",
+
"name": "ID",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "maxCP",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Int",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "maxHP",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Int",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "name",
+
"type": {
+
"kind": "NON_NULL",
+
"ofType": {
+
"kind": "SCALAR",
+
"name": "String",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "resistant",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "ENUM",
+
"name": "PokemonType",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "types",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "ENUM",
+
"name": "PokemonType",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "weaknesses",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "ENUM",
+
"name": "PokemonType",
+
"ofType": null
+
}
+
},
+
"args": []
+
},
+
{
+
"name": "weight",
+
"type": {
+
"kind": "OBJECT",
+
"name": "PokemonDimension",
+
"ofType": null
+
},
+
"args": []
+
}
+
],
+
"interfaces": []
+
},
+
{
+
"kind": "SCALAR",
+
"name": "Float"
+
},
+
{
+
"kind": "SCALAR",
+
"name": "ID"
+
},
+
{
+
"kind": "OBJECT",
+
"name": "PokemonDimension",
+
"fields": [
+
{
+
"name": "maximum",
+
"type": {
+
"kind": "SCALAR",
+
"name": "String",
+
"ofType": null
+
},
+
"args": []
+
},
+
{
+
"name": "minimum",
+
"type": {
+
"kind": "SCALAR",
+
"name": "String",
+
"ofType": null
+
},
+
"args": []
+
}
+
],
+
"interfaces": []
+
},
+
{
+
"kind": "ENUM",
+
"name": "PokemonType",
+
"enumValues": [
+
{
+
"name": "Bug"
+
},
+
{
+
"name": "Dark"
+
},
+
{
+
"name": "Dragon"
+
},
+
{
+
"name": "Electric"
+
},
+
{
+
"name": "Fairy"
+
},
+
{
+
"name": "Fighting"
+
},
+
{
+
"name": "Fire"
+
},
+
{
+
"name": "Flying"
+
},
+
{
+
"name": "Ghost"
+
},
+
{
+
"name": "Grass"
+
},
+
{
+
"name": "Ground"
+
},
+
{
+
"name": "Ice"
+
},
+
{
+
"name": "Normal"
+
},
+
{
+
"name": "Poison"
+
},
+
{
+
"name": "Psychic"
+
},
+
{
+
"name": "Rock"
+
},
+
{
+
"name": "Steel"
+
},
+
{
+
"name": "Water"
+
}
+
]
+
},
+
{
+
"kind": "OBJECT",
+
"name": "Query",
+
"fields": [
+
{
+
"name": "pokemon",
+
"type": {
+
"kind": "OBJECT",
+
"name": "Pokemon",
+
"ofType": null
+
},
+
"args": [
+
{
+
"name": "id",
+
"type": {
+
"kind": "NON_NULL",
+
"ofType": {
+
"kind": "SCALAR",
+
"name": "ID",
+
"ofType": null
+
}
+
}
+
}
+
]
+
},
+
{
+
"name": "pokemons",
+
"type": {
+
"kind": "LIST",
+
"ofType": {
+
"kind": "OBJECT",
+
"name": "Pokemon",
+
"ofType": null
+
}
+
},
+
"args": [
+
{
+
"name": "limit",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Int",
+
"ofType": null
+
}
+
},
+
{
+
"name": "skip",
+
"type": {
+
"kind": "SCALAR",
+
"name": "Int",
+
"ofType": null
+
}
+
}
+
]
+
}
+
],
+
"interfaces": []
+
},
+
{
+
"kind": "SCALAR",
+
"name": "Boolean"
+
}
+
],
+
"directives": []
+
}
+
};
+
+
import * as gqlTada from 'gql.tada';
+
+
declare module 'gql.tada' {
+
interface setupSchema {
+
introspection: introspection;
+
}
+
}
+14
test/e2e/fixture-project-tada/package.json
···
+
{
+
"name": "fixtures",
+
"private": true,
+
"dependencies": {
+
"graphql": "^16.0.0",
+
"gql.tada": "^1.2.1",
+
"@graphql-typed-document-node/core": "^3.0.0",
+
"@0no-co/graphqlsp": "workspace:*",
+
"@urql/core": "^4.0.4"
+
},
+
"devDependencies": {
+
"typescript": "^5.3.3"
+
}
+
}
+94
test/e2e/fixture-project-tada/schema.graphql
···
+
### This file was generated by Nexus Schema
+
### Do not make changes to this file directly
+
+
"""
+
Move a Pokémon can perform with the associated damage and type.
+
"""
+
type Attack {
+
damage: Int
+
name: String
+
type: PokemonType
+
}
+
+
type AttacksConnection {
+
fast: [Attack]
+
special: [Attack]
+
}
+
+
"""
+
Requirement that prevents an evolution through regular means of levelling up.
+
"""
+
type EvolutionRequirement {
+
amount: Int
+
name: String
+
}
+
+
type Pokemon {
+
attacks: AttacksConnection
+
classification: String @deprecated(reason: "And this is the reason why")
+
evolutionRequirements: [EvolutionRequirement]
+
evolutions: [Pokemon]
+
+
"""
+
Likelihood of an attempt to catch a Pokémon to fail.
+
"""
+
fleeRate: Float
+
height: PokemonDimension
+
id: ID!
+
+
"""
+
Maximum combat power a Pokémon may achieve at max level.
+
"""
+
maxCP: Int
+
+
"""
+
Maximum health points a Pokémon may achieve at max level.
+
"""
+
maxHP: Int
+
name: String!
+
resistant: [PokemonType]
+
types: [PokemonType]
+
weaknesses: [PokemonType]
+
weight: PokemonDimension
+
}
+
+
type PokemonDimension {
+
maximum: String
+
minimum: String
+
}
+
+
"""
+
Elemental property associated with either a Pokémon or one of their moves.
+
"""
+
enum PokemonType {
+
Bug
+
Dark
+
Dragon
+
Electric
+
Fairy
+
Fighting
+
Fire
+
Flying
+
Ghost
+
Grass
+
Ground
+
Ice
+
Normal
+
Poison
+
Psychic
+
Rock
+
Steel
+
Water
+
}
+
+
type Query {
+
"""
+
Get a single Pokémon by its ID, a three character long identifier padded with zeroes
+
"""
+
pokemon(id: ID!): Pokemon
+
+
"""
+
List out all Pokémon, optionally in pages
+
"""
+
pokemons(limit: Int, skip: Int): [Pokemon]
+
}
+18
test/e2e/fixture-project-tada/tsconfig.json
···
+
{
+
"compilerOptions": {
+
"plugins": [
+
{
+
"name": "@0no-co/graphqlsp",
+
"schema": "./schema.graphql",
+
"tadaOutputLocation": "./introspection.d.ts"
+
}
+
],
+
"target": "es2016",
+
"esModuleInterop": true,
+
"moduleResolution": "node",
+
"forceConsistentCasingInFileNames": true,
+
"strict": true,
+
"skipLibCheck": true
+
},
+
"exclude": ["node_modules", "fixtures"]
+
}
+370
test/e2e/tada.test.ts
···
+
import { expect, afterAll, beforeAll, it, describe } from 'vitest';
+
import { TSServer } from './server';
+
import path from 'node:path';
+
import fs from 'node:fs';
+
import url from 'node:url';
+
import ts from 'typescript/lib/tsserverlibrary';
+
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
+
+
const projectPath = path.resolve(__dirname, 'fixture-project-tada');
+
describe('Fragment + operations', () => {
+
const outfileCombo = path.join(projectPath, 'simple.ts');
+
const outfileUnusedFragment = path.join(projectPath, 'unused-fragment.ts');
+
const outfileCombinations = path.join(projectPath, 'fragment.ts');
+
+
let server: TSServer;
+
beforeAll(async () => {
+
server = new TSServer(projectPath, { debugLog: false });
+
+
server.sendCommand('open', {
+
file: outfileCombo,
+
fileContent: '// empty',
+
scriptKindName: 'TS',
+
} satisfies ts.server.protocol.OpenRequestArgs);
+
server.sendCommand('open', {
+
file: outfileCombinations,
+
fileContent: '// empty',
+
scriptKindName: 'TS',
+
} satisfies ts.server.protocol.OpenRequestArgs);
+
server.sendCommand('open', {
+
file: outfileUnusedFragment,
+
fileContent: '// empty',
+
scriptKindName: 'TS',
+
} satisfies ts.server.protocol.OpenRequestArgs);
+
+
server.sendCommand('updateOpen', {
+
openFiles: [
+
{
+
file: outfileCombinations,
+
fileContent: fs.readFileSync(
+
path.join(projectPath, 'fixtures/fragment.ts'),
+
'utf-8'
+
),
+
},
+
{
+
file: outfileCombo,
+
fileContent: fs.readFileSync(
+
path.join(projectPath, 'fixtures/simple.ts'),
+
'utf-8'
+
),
+
},
+
{
+
file: outfileUnusedFragment,
+
fileContent: fs.readFileSync(
+
path.join(projectPath, 'fixtures/unused-fragment.ts'),
+
'utf-8'
+
),
+
},
+
],
+
} satisfies ts.server.protocol.UpdateOpenRequestArgs);
+
+
server.sendCommand('saveto', {
+
file: outfileCombo,
+
tmpfile: outfileCombo,
+
} satisfies ts.server.protocol.SavetoRequestArgs);
+
server.sendCommand('saveto', {
+
file: outfileCombinations,
+
tmpfile: outfileCombinations,
+
} satisfies ts.server.protocol.SavetoRequestArgs);
+
server.sendCommand('saveto', {
+
file: outfileUnusedFragment,
+
tmpfile: outfileUnusedFragment,
+
} satisfies ts.server.protocol.SavetoRequestArgs);
+
});
+
+
afterAll(() => {
+
try {
+
fs.unlinkSync(outfileUnusedFragment);
+
fs.unlinkSync(outfileCombinations);
+
fs.unlinkSync(outfileCombo);
+
} catch {}
+
});
+
+
it('gives semantic-diagnostics with preceding fragments', async () => {
+
await server.waitForResponse(
+
e => e.type === 'event' && e.event === 'semanticDiag'
+
);
+
const res = server.responses.filter(
+
resp =>
+
resp.type === 'event' &&
+
resp.event === 'semanticDiag' &&
+
resp.body?.file === outfileCombo
+
);
+
expect(res[0].body.diagnostics).toMatchInlineSnapshot(`
+
[
+
{
+
"category": "warning",
+
"code": 52004,
+
"end": {
+
"line": 12,
+
"offset": 1,
+
},
+
"start": {
+
"line": 11,
+
"offset": 7,
+
},
+
"text": "The field Pokemon.classification is deprecated. And this is the reason why",
+
},
+
]
+
`);
+
}, 30000);
+
+
it('gives quick-info with preceding fragments', async () => {
+
server.send({
+
seq: 9,
+
type: 'request',
+
command: 'quickinfo',
+
arguments: {
+
file: outfileCombinations,
+
line: 7,
+
offset: 8,
+
},
+
});
+
+
await server.waitForResponse(
+
response =>
+
response.type === 'response' && response.command === 'quickinfo'
+
);
+
+
const res = server.responses
+
.reverse()
+
.find(resp => resp.type === 'response' && resp.command === 'quickinfo');
+
+
expect(res).toBeDefined();
+
expect(typeof res?.body).toEqual('object');
+
expect(res?.body.documentation).toEqual(`Pokemon.name: String!`);
+
}, 30000);
+
+
it('gives quick-info with documents', async () => {
+
server.send({
+
seq: 9,
+
type: 'request',
+
command: 'quickinfo',
+
arguments: {
+
file: outfileCombo,
+
line: 7,
+
offset: 10,
+
},
+
});
+
+
await server.waitForResponse(
+
response =>
+
response.type === 'response' && response.command === 'quickinfo'
+
);
+
+
const res = server.responses
+
.reverse()
+
.find(resp => resp.type === 'response' && resp.command === 'quickinfo');
+
+
expect(res).toBeDefined();
+
expect(typeof res?.body).toEqual('object');
+
expect(res?.body.documentation).toEqual(
+
`Query.pokemons: [Pokemon]
+
+
List out all Pokémon, optionally in pages`
+
);
+
}, 30000);
+
+
it('gives suggestions with preceding fragments', async () => {
+
server.send({
+
seq: 10,
+
type: 'request',
+
command: 'completionInfo',
+
arguments: {
+
file: outfileCombinations,
+
line: 8,
+
offset: 5,
+
includeExternalModuleExports: true,
+
includeInsertTextCompletions: true,
+
triggerKind: 1,
+
},
+
});
+
+
await server.waitForResponse(
+
response =>
+
response.type === 'response' && response.command === 'completionInfo'
+
);
+
+
const res = server.responses
+
.reverse()
+
.find(
+
resp => resp.type === 'response' && resp.command === 'completionInfo'
+
);
+
+
expect(res).toBeDefined();
+
expect(typeof res?.body.entries).toEqual('object');
+
expect(res?.body.entries).toMatchInlineSnapshot(`
+
[
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " AttacksConnection",
+
},
+
"name": "attacks",
+
"sortText": "0attacks",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " [EvolutionRequirement]",
+
},
+
"name": "evolutionRequirements",
+
"sortText": "2evolutionRequirements",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " [Pokemon]",
+
},
+
"name": "evolutions",
+
"sortText": "3evolutions",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"description": "Likelihood of an attempt to catch a Pokémon to fail.",
+
"detail": " Float",
+
},
+
"name": "fleeRate",
+
"sortText": "4fleeRate",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " PokemonDimension",
+
},
+
"name": "height",
+
"sortText": "5height",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " ID!",
+
},
+
"name": "id",
+
"sortText": "6id",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"description": "Maximum combat power a Pokémon may achieve at max level.",
+
"detail": " Int",
+
},
+
"name": "maxCP",
+
"sortText": "7maxCP",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"description": "Maximum health points a Pokémon may achieve at max level.",
+
"detail": " Int",
+
},
+
"name": "maxHP",
+
"sortText": "8maxHP",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " String!",
+
},
+
"name": "name",
+
"sortText": "9name",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " [PokemonType]",
+
},
+
"name": "resistant",
+
"sortText": "10resistant",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " [PokemonType]",
+
},
+
"name": "types",
+
"sortText": "11types",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " [PokemonType]",
+
},
+
"name": "weaknesses",
+
"sortText": "12weaknesses",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"detail": " PokemonDimension",
+
},
+
"name": "weight",
+
"sortText": "13weight",
+
},
+
{
+
"kind": "var",
+
"kindModifiers": "declare",
+
"labelDetails": {
+
"description": "The name of the current Object type at runtime.",
+
"detail": " String!",
+
},
+
"name": "__typename",
+
"sortText": "14__typename",
+
},
+
]
+
`);
+
}, 30000);
+
+
it('gives semantic-diagnostics with unused fragments', async () => {
+
server.sendCommand('saveto', {
+
file: outfileUnusedFragment,
+
tmpfile: outfileUnusedFragment,
+
} satisfies ts.server.protocol.SavetoRequestArgs);
+
+
await server.waitForResponse(
+
e =>
+
e.type === 'event' &&
+
e.event === 'semanticDiag' &&
+
e.body?.file === outfileUnusedFragment
+
);
+
+
const res = server.responses.filter(
+
resp =>
+
resp.type === 'event' &&
+
resp.event === 'semanticDiag' &&
+
resp.body?.file === outfileUnusedFragment
+
);
+
expect(res[0].body.diagnostics).toMatchInlineSnapshot(`
+
[
+
{
+
"category": "warning",
+
"code": 52003,
+
"end": {
+
"line": 2,
+
"offset": 37,
+
},
+
"start": {
+
"line": 2,
+
"offset": 25,
+
},
+
"text": "Unused co-located fragment definition(s) \\"pokemonFields\\" in './fragment'",
+
},
+
]
+
`);
+
}, 30000);
+
});