diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68558a5f..66d72875 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,6 +260,28 @@ importers: specifier: ^5.3.3 version: 5.3.3 + test/e2e/fixture-project-tada-multi-schema: + 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.5.9-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b + version: 1.5.9-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b(graphql@16.8.1)(svelte@4.2.15)(typescript@5.3.3) + 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': @@ -310,7 +332,7 @@ packages: /@0no-co/graphqlsp@1.11.0(typescript@5.3.3): resolution: {integrity: sha512-P8DRsT+pRgXXZ+8szO1ISUXLxtaL9ukKddjLqSh+oBvWVCzUDyUM4Une0Co0Y7XC017wI4pdcrR/3hWqw9uuDg==} peerDependencies: - typescript: ^5.0.0 + typescript: ^5.3.3 dependencies: '@gql.tada/internal': 0.1.3(graphql@16.8.1)(typescript@5.3.3) graphql: 16.8.1 @@ -1379,7 +1401,7 @@ packages: /@gql.tada/cli-utils@1.2.3-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b(svelte@4.2.15)(typescript@5.3.3): resolution: {integrity: sha512-tCKCGD4VmdSrnt7wvvzuJ1dtDf0nB6rBlYkOAvgmwDzDQgkWnwP97Xv6t6i1HAWiE9f9uzMl8y1eAr4IbySPrQ==} peerDependencies: - typescript: ^5.0.0 + typescript: ^5.3.3 dependencies: '@0no-co/graphqlsp': 1.11.0(typescript@5.3.3) '@gql.tada/internal': 0.3.0-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b(graphql@16.8.1)(typescript@5.3.3) @@ -1397,7 +1419,7 @@ packages: resolution: {integrity: sha512-wIvykBId7O0gaizmSl5n5AhbQsgJVLTUsFBm3RsfQ9dVfpmT+Fhy2yHX+yNgiVECg2EimXMhs4ltcE4EuZ2WOA==} peerDependencies: graphql: ^16.8.1 - typescript: ^5.0.0 + typescript: ^5.3.3 dependencies: '@0no-co/graphql.web': 1.0.6(graphql@16.8.1) graphql: 16.8.1 @@ -1408,7 +1430,7 @@ packages: resolution: {integrity: sha512-CPx00hSOue+XK1KUZhovuN0sZ3sCcUx99Y/iv9obL8ea2rLJ7HbehJ1uI8zwLVtfPoX/tLz+Cc34TFF1znSR7Q==} peerDependencies: graphql: ^16.8.1 - typescript: ^5.0.0 + typescript: ^5.3.3 dependencies: '@0no-co/graphql.web': 1.0.6(graphql@16.8.1) graphql: 16.8.1 @@ -2294,7 +2316,7 @@ packages: peerDependencies: rollup: ^2.14.0||^3.0.0||^4.0.0 tslib: '*' - typescript: '>=3.7.0' + typescript: ^5.3.3 peerDependenciesMeta: rollup: optional: true @@ -2628,7 +2650,7 @@ packages: /@vue/language-core@2.0.14(typescript@5.3.3): resolution: {integrity: sha512-3q8mHSNcGTR7sfp2X6jZdcb4yt8AjBXAfKk0qkZIh7GAJxOnoZ10h5HToZglw4ToFvAnq+xu/Z2FFbglh9Icag==} peerDependencies: - typescript: '*' + typescript: ^5.3.3 peerDependenciesMeta: typescript: optional: true @@ -3282,7 +3304,7 @@ packages: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: - typescript: '>=4.9.5' + typescript: ^5.3.3 peerDependenciesMeta: typescript: optional: true @@ -3956,7 +3978,7 @@ packages: resolution: {integrity: sha512-3tglGLiGY1zyMyZAow2kpy8GBi35xGAGiEDiPAIPXOUyU35B0HpY0lmMZhf5jEs0fv2FTMgWvFXO6Z3378B6FA==} hasBin: true peerDependencies: - typescript: ^5.0.0 + typescript: ^5.3.3 dependencies: '@0no-co/graphql.web': 1.0.6(graphql@16.8.1) '@gql.tada/cli-utils': 1.2.3-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b(svelte@4.2.15)(typescript@5.3.3) @@ -5483,7 +5505,7 @@ packages: engines: {node: '>=16'} peerDependencies: rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 + typescript: ^5.3.3 dependencies: magic-string: 0.30.5 rollup: 4.9.5 @@ -5908,7 +5930,7 @@ packages: resolution: {integrity: sha512-HAIxtk5TUHXvCRKApKfxoh1BGT85S/17lS3DvbfxRKFd+Ghr5YScqBvd+sU+p7vJFw48LNkzdFk+ooNVk3e4kA==} peerDependencies: svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 - typescript: ^4.9.4 || ^5.0.0 + typescript: ^5.3.3 dependencies: dedent-js: 1.0.1 pascal-case: 3.1.2 @@ -6019,7 +6041,7 @@ packages: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' '@types/node': '*' - typescript: '>=2.7' + typescript: ^5.3.3 peerDependenciesMeta: '@swc/core': optional: true @@ -6588,7 +6610,7 @@ packages: id: file:packages/graphqlsp name: '@0no-co/graphqlsp' peerDependencies: - typescript: ^5.0.0 + typescript: ^5.3.3 dependencies: '@gql.tada/internal': 0.3.0-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b(graphql@16.8.1)(typescript@5.3.3) graphql: 16.8.1 diff --git a/test/e2e/fixture-project-tada-multi-schema/.vscode/settings.json b/test/e2e/fixture-project-tada-multi-schema/.vscode/settings.json new file mode 100644 index 00000000..25fa6215 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/test/e2e/fixture-project-tada-multi-schema/fixtures/pokemon.ts b/test/e2e/fixture-project-tada-multi-schema/fixtures/pokemon.ts new file mode 100644 index 00000000..1700ee09 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/fixtures/pokemon.ts @@ -0,0 +1,9 @@ +import { initGraphQLTada } from 'gql.tada'; +import type { introspection } from '../pokemons'; + +export const graphql = initGraphQLTada<{ + introspection: introspection; +}>(); + +export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; +export { readFragment } from 'gql.tada'; diff --git a/test/e2e/fixture-project-tada-multi-schema/fixtures/simple-pokemon.ts b/test/e2e/fixture-project-tada-multi-schema/fixtures/simple-pokemon.ts new file mode 100644 index 00000000..a937ac65 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/fixtures/simple-pokemon.ts @@ -0,0 +1,15 @@ +import { graphql } from './pokemon'; + +// prettier-ignore +const x = graphql(` + query Pokemons($limit: Int!) { + pokemons(limit: $limit) { + id + name + + fleeRate + classification + __typename + } + } +`); diff --git a/test/e2e/fixture-project-tada-multi-schema/fixtures/simple-todo.ts b/test/e2e/fixture-project-tada-multi-schema/fixtures/simple-todo.ts new file mode 100644 index 00000000..61f69fd0 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/fixtures/simple-todo.ts @@ -0,0 +1,11 @@ +import { graphql } from './todo'; + +// prettier-ignore +const x = graphql(` + query Todo($id: ID!) { + todo(id: $id) { + id + + } + } +`); diff --git a/test/e2e/fixture-project-tada-multi-schema/fixtures/todo.ts b/test/e2e/fixture-project-tada-multi-schema/fixtures/todo.ts new file mode 100644 index 00000000..e5846427 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/fixtures/todo.ts @@ -0,0 +1,9 @@ +import { initGraphQLTada } from 'gql.tada'; +import type { introspection } from '../todos'; + +export const graphql = initGraphQLTada<{ + introspection: introspection; +}>(); + +export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; +export { readFragment } from 'gql.tada'; diff --git a/test/e2e/fixture-project-tada-multi-schema/package.json b/test/e2e/fixture-project-tada-multi-schema/package.json new file mode 100644 index 00000000..06d34c43 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/package.json @@ -0,0 +1,14 @@ +{ + "name": "fixtures", + "private": true, + "dependencies": { + "graphql": "^16.0.0", + "gql.tada": "1.5.9-canary-8711af177005f46fa3e06d990b6ba28e353e7f9b", + "@graphql-typed-document-node/core": "^3.0.0", + "@0no-co/graphqlsp": "workspace:*", + "@urql/core": "^4.0.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + } +} diff --git a/test/e2e/fixture-project-tada-multi-schema/pokemon.ts b/test/e2e/fixture-project-tada-multi-schema/pokemon.ts new file mode 100644 index 00000000..5ccb7ce0 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/pokemon.ts @@ -0,0 +1,9 @@ +import { initGraphQLTada } from 'gql.tada'; +import type { introspection } from './pokemons'; + +export const graphql = initGraphQLTada<{ + introspection: introspection; +}>(); + +export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; +export { readFragment } from 'gql.tada'; diff --git a/test/e2e/fixture-project-tada-multi-schema/pokemons.d.ts b/test/e2e/fixture-project-tada-multi-schema/pokemons.d.ts new file mode 100644 index 00000000..19817a43 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/pokemons.d.ts @@ -0,0 +1,33 @@ +/* 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 = { + name: 'pokemons'; + query: 'Query'; + mutation: never; + subscription: never; + types: { + 'Attack': { kind: 'OBJECT'; name: 'Attack'; fields: { 'damage': { name: 'damage'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; } }; }; }; + 'AttacksConnection': { kind: 'OBJECT'; name: 'AttacksConnection'; fields: { 'fast': { name: 'fast'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; 'special': { name: 'special'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; }; }; + 'Boolean': unknown; + 'EvolutionRequirement': { kind: 'OBJECT'; name: 'EvolutionRequirement'; fields: { 'amount': { name: 'amount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'Float': unknown; + 'ID': unknown; + 'Int': unknown; + 'Pokemon': { kind: 'OBJECT'; name: 'Pokemon'; fields: { 'attacks': { name: 'attacks'; type: { kind: 'OBJECT'; name: 'AttacksConnection'; ofType: null; } }; 'classification': { name: 'classification'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'evolutionRequirements': { name: 'evolutionRequirements'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'EvolutionRequirement'; ofType: null; }; } }; 'evolutions': { name: 'evolutions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; 'fleeRate': { name: 'fleeRate'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'height': { name: 'height'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'maxCP': { name: 'maxCP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'maxHP': { name: 'maxHP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'resistant': { name: 'resistant'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'types': { name: 'types'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weaknesses': { name: 'weaknesses'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weight': { name: 'weight'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; }; }; + 'PokemonDimension': { kind: 'OBJECT'; name: 'PokemonDimension'; fields: { 'maximum': { name: 'maximum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'minimum': { name: 'minimum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'PokemonType': { name: 'PokemonType'; enumValues: 'Bug' | 'Dark' | 'Dragon' | 'Electric' | 'Fairy' | 'Fighting' | 'Fire' | 'Flying' | 'Ghost' | 'Grass' | 'Ground' | 'Ice' | 'Normal' | 'Poison' | 'Psychic' | 'Rock' | 'Steel' | 'Water'; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'pokemon': { name: 'pokemon'; type: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; } }; 'pokemons': { name: 'pokemons'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; }; }; + 'String': unknown; + }; +}; + +import * as gqlTada from 'gql.tada'; diff --git a/test/e2e/fixture-project-tada-multi-schema/pokemons.graphql b/test/e2e/fixture-project-tada-multi-schema/pokemons.graphql new file mode 100644 index 00000000..148f0b7c --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/pokemons.graphql @@ -0,0 +1,94 @@ +### 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] +} \ No newline at end of file diff --git a/test/e2e/fixture-project-tada-multi-schema/todo.ts b/test/e2e/fixture-project-tada-multi-schema/todo.ts new file mode 100644 index 00000000..dfcaa9fe --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/todo.ts @@ -0,0 +1,10 @@ +import { initGraphQLTada } from 'gql.tada'; +// @ts-ignore +import type { introspection } from './todos'; + +export const graphql = initGraphQLTada<{ + introspection: introspection; +}>(); + +export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; +export { readFragment } from 'gql.tada'; diff --git a/test/e2e/fixture-project-tada-multi-schema/todos.d.ts b/test/e2e/fixture-project-tada-multi-schema/todos.d.ts new file mode 100644 index 00000000..cc37e693 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/todos.d.ts @@ -0,0 +1,26 @@ +/* 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 = { + name: 'todos'; + query: 'Query'; + mutation: never; + subscription: never; + types: { + 'Boolean': unknown; + 'ID': unknown; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'todo': { name: 'todo'; type: { kind: 'OBJECT'; name: 'Todo'; ofType: null; } }; }; }; + 'String': unknown; + 'Todo': { kind: 'OBJECT'; name: 'Todo'; fields: { 'completed': { name: 'completed'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'text': { name: 'text'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; + }; +}; + +import * as gqlTada from 'gql.tada'; diff --git a/test/e2e/fixture-project-tada-multi-schema/todos.graphql b/test/e2e/fixture-project-tada-multi-schema/todos.graphql new file mode 100644 index 00000000..5f98178c --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/todos.graphql @@ -0,0 +1,12 @@ +type Todo { + id: ID! + text: String! + completed: Boolean! +} + +type Query { + """ + Get a single Todo by its ID + """ + todo(id: ID!): Todo +} \ No newline at end of file diff --git a/test/e2e/fixture-project-tada-multi-schema/tsconfig.json b/test/e2e/fixture-project-tada-multi-schema/tsconfig.json new file mode 100644 index 00000000..c1d60026 --- /dev/null +++ b/test/e2e/fixture-project-tada-multi-schema/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "plugins": [ + { + "name": "@0no-co/graphqlsp", + "schemas": [ + { + "name": "pokemons", + "schema": "./pokemons.graphql", + "tadaOutputLocation": "./pokemons.d.ts" + }, + { + "name": "todos", + "schema": "./todos.graphql", + "tadaOutputLocation": "./todos.d.ts" + } + ] + } + ], + "target": "es2016", + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "exclude": ["node_modules", "fixtures"] +} diff --git a/test/e2e/fixture-project-tada/introspection.d.ts b/test/e2e/fixture-project-tada/introspection.d.ts index a2653c7c..22ac94ee 100644 --- a/test/e2e/fixture-project-tada/introspection.d.ts +++ b/test/e2e/fixture-project-tada/introspection.d.ts @@ -10,428 +10,24 @@ * 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": [] - } + name: never; + query: 'Query'; + mutation: never; + subscription: never; + types: { + 'Attack': { kind: 'OBJECT'; name: 'Attack'; fields: { 'damage': { name: 'damage'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; } }; }; }; + 'AttacksConnection': { kind: 'OBJECT'; name: 'AttacksConnection'; fields: { 'fast': { name: 'fast'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; 'special': { name: 'special'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; }; }; + 'Boolean': unknown; + 'EvolutionRequirement': { kind: 'OBJECT'; name: 'EvolutionRequirement'; fields: { 'amount': { name: 'amount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'Float': unknown; + 'ID': unknown; + 'Int': unknown; + 'Pokemon': { kind: 'OBJECT'; name: 'Pokemon'; fields: { 'attacks': { name: 'attacks'; type: { kind: 'OBJECT'; name: 'AttacksConnection'; ofType: null; } }; 'classification': { name: 'classification'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'evolutionRequirements': { name: 'evolutionRequirements'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'EvolutionRequirement'; ofType: null; }; } }; 'evolutions': { name: 'evolutions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; 'fleeRate': { name: 'fleeRate'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'height': { name: 'height'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'maxCP': { name: 'maxCP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'maxHP': { name: 'maxHP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'resistant': { name: 'resistant'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'types': { name: 'types'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weaknesses': { name: 'weaknesses'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weight': { name: 'weight'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; }; }; + 'PokemonDimension': { kind: 'OBJECT'; name: 'PokemonDimension'; fields: { 'maximum': { name: 'maximum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'minimum': { name: 'minimum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'PokemonType': { name: 'PokemonType'; enumValues: 'Bug' | 'Dark' | 'Dragon' | 'Electric' | 'Fairy' | 'Fighting' | 'Fire' | 'Flying' | 'Ghost' | 'Grass' | 'Ground' | 'Ice' | 'Normal' | 'Poison' | 'Psychic' | 'Rock' | 'Steel' | 'Water'; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'pokemon': { name: 'pokemon'; type: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; } }; 'pokemons': { name: 'pokemons'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; }; }; + 'String': unknown; + }; }; import * as gqlTada from 'gql.tada'; - -declare module 'gql.tada' { - interface setupSchema { - introspection: introspection; - } -} diff --git a/test/e2e/multi-schema-tada.test.ts b/test/e2e/multi-schema-tada.test.ts new file mode 100644 index 00000000..6c8b2958 --- /dev/null +++ b/test/e2e/multi-schema-tada.test.ts @@ -0,0 +1,364 @@ +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-multi-schema' +); +describe('Multiple schemas', () => { + const outfilePokemonTest = path.join(projectPath, 'simple-pokemon.ts'); + const outfileTodoTest = path.join(projectPath, 'simple-todo.ts'); + + let server: TSServer; + beforeAll(async () => { + server = new TSServer(projectPath, { debugLog: false }); + + server.sendCommand('open', { + file: outfilePokemonTest, + fileContent: '// empty', + scriptKindName: 'TS', + } satisfies ts.server.protocol.OpenRequestArgs); + server.sendCommand('open', { + file: outfileTodoTest, + fileContent: '// empty', + scriptKindName: 'TS', + } satisfies ts.server.protocol.OpenRequestArgs); + + server.sendCommand('updateOpen', { + openFiles: [ + { + file: outfilePokemonTest, + fileContent: fs.readFileSync( + path.join(projectPath, 'fixtures/simple-pokemon.ts'), + 'utf-8' + ), + }, + { + file: outfileTodoTest, + fileContent: fs.readFileSync( + path.join(projectPath, 'fixtures/simple-todo.ts'), + 'utf-8' + ), + }, + ], + } satisfies ts.server.protocol.UpdateOpenRequestArgs); + + server.sendCommand('saveto', { + file: outfilePokemonTest, + tmpfile: outfilePokemonTest, + } satisfies ts.server.protocol.SavetoRequestArgs); + server.sendCommand('saveto', { + file: outfileTodoTest, + tmpfile: outfileTodoTest, + } satisfies ts.server.protocol.SavetoRequestArgs); + + // Give TS some time to figure this out... + await new Promise(resolve => setTimeout(resolve, 1000)); + }); + + afterAll(() => { + try { + fs.unlinkSync(outfilePokemonTest); + fs.unlinkSync(outfileTodoTest); + } catch {} + }); + + it('gives diagnostics about unused fields', 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 === outfilePokemonTest + ); + + expect(res).toBeDefined(); + expect(res).toHaveLength(1); + expect(res[0].body.diagnostics).toHaveLength(1); + expect(res[0].body.diagnostics[0]).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 for the pokemon document', async () => { + server.send({ + seq: 9, + type: 'request', + command: 'quickinfo', + arguments: { + file: outfilePokemonTest, + line: 8, + 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 for the todo document', async () => { + server.send({ + seq: 10, + type: 'request', + command: 'quickinfo', + arguments: { + file: outfileTodoTest, + 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(`Todo.id: ID!`); + }, 30000); + + it('gives completion-info for the pokemon document', async () => { + server.send({ + seq: 11, + type: 'request', + command: 'completionInfo', + arguments: { + file: outfilePokemonTest, + line: 9, + offset: 7, + 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(res).toMatchInlineSnapshot(` + { + "body": { + "entries": [ + { + "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": { + "detail": " PokemonDimension", + }, + "name": "height", + "sortText": "5height", + }, + { + "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": " [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", + }, + ], + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + }, + "command": "completionInfo", + "request_seq": 11, + "seq": 0, + "success": true, + "type": "response", + } + `); + }, 30000); + + it('gives completion-info for the todo document', async () => { + server.send({ + seq: 11, + type: 'request', + command: 'completionInfo', + arguments: { + file: outfileTodoTest, + line: 8, + offset: 7, + 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(res).toMatchInlineSnapshot(` + { + "body": { + "entries": [ + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " String!", + }, + "name": "text", + "sortText": "1text", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "detail": " Boolean!", + }, + "name": "completed", + "sortText": "2completed", + }, + { + "kind": "var", + "kindModifiers": "declare", + "labelDetails": { + "description": "The name of the current Object type at runtime.", + "detail": " String!", + }, + "name": "__typename", + "sortText": "3__typename", + }, + ], + "isGlobalCompletion": false, + "isMemberCompletion": false, + "isNewIdentifierLocation": false, + }, + "command": "completionInfo", + "request_seq": 11, + "seq": 0, + "success": true, + "type": "response", + } + `); + }, 30000); +});