From ae18ce11a5695aa5e2194c42dfe66c73249e238b Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sun, 5 Jul 2026 15:11:33 +0200 Subject: [PATCH 1/2] chore: test against TypeScript 6 Bump the workspace's TypeScript override and devDependency ranges from 5.9 to 6.0.3. No source changes are needed: the plugin's peer range already advertises ^6.0.0, tsserverlibrary still ships as the merged re-export shim, and all peers (rollup-plugin-dts, @gql.tada/internal, gql.tada) already allow TS 6. The one casualty was the e2e suites that asserted on the first automatically emitted semanticDiag event: TS 6's tsserver computes those diagnostics fast enough to consistently beat the plugin's asynchronous schema load, so the events arrived empty. Extract the geterr-polling helper the misconfiguration suite already used into util.ts as pollDiagnostics and use it everywhere diagnostics are asserted, instead of relying on spontaneous events. This also removes the long-standing intermittent flakiness of those suites on TS 5 and cuts the suite's runtime roughly in half, since polling resolves as soon as the schema is ready. Co-Authored-By: Claude Fable 5 --- package.json | 2 +- packages/graphqlsp/package.json | 2 +- pnpm-lock.yaml | 183 ++++++++++++++--------------- pnpm-workspace.yaml | 2 +- test/e2e/client-preset.test.ts | 29 +---- test/e2e/combinations.test.ts | 29 +---- test/e2e/misconfiguration.test.ts | 31 +---- test/e2e/multi-schema-tada.test.ts | 17 +-- test/e2e/persisted-tada.test.ts | 13 +- test/e2e/tada.test.ts | 89 ++++---------- test/e2e/unused-fieds.test.ts | 131 ++++----------------- test/e2e/util.ts | 39 ++++++ 12 files changed, 187 insertions(+), 380 deletions(-) diff --git a/package.json b/package.json index 54635868..ba52207a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "rollup": "^4.62.2", "rollup-plugin-cjs-check": "^1.0.3", "rollup-plugin-dts": "^6.4.1", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "vite": "^8.1.3", "vitest": "^4.1.9" } diff --git a/packages/graphqlsp/package.json b/packages/graphqlsp/package.json index 962c1f99..d70571b0 100644 --- a/packages/graphqlsp/package.json +++ b/packages/graphqlsp/package.json @@ -47,7 +47,7 @@ "@types/node": "^22.20.0", "graphql-language-service": "^5.5.2", "lru-cache": "^11.5.1", - "typescript": "^5.9.3" + "typescript": "^6.0.3" }, "dependencies": { "@gql.tada/internal": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e45492..1b4a3d0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - typescript: ^5.3.3 + typescript: ^6.0.3 ua-parser-js@<0.7.33: '>=0.7.33' postcss@<8.4.31: '>=8.4.31' semver@<5.7.2: '>=5.7.2' @@ -53,7 +53,7 @@ importers: version: 1.0.0(rollup@4.62.2) '@rollup/plugin-typescript': specifier: ^12.3.0 - version: 12.3.0(rollup@4.62.2)(tslib@2.8.1)(typescript@5.9.3) + version: 12.3.0(rollup@4.62.2)(tslib@2.8.1)(typescript@6.0.3) '@types/node': specifier: ^22.20.0 version: 22.20.0 @@ -77,10 +77,10 @@ importers: version: 1.0.3(rollup@4.62.2) rollup-plugin-dts: specifier: ^6.4.1 - version: 6.4.1(rollup@4.62.2)(typescript@5.9.3) + version: 6.4.1(rollup@4.62.2)(typescript@6.0.3) typescript: - specifier: ^5.3.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 vite: specifier: ^8.1.3 version: 8.1.3(@types/node@22.20.0)(jiti@2.6.1)(terser@5.48.0)(yaml@2.9.0) @@ -102,10 +102,10 @@ importers: devDependencies: '@0no-co/graphqlsp': specifier: file:../graphqlsp - version: file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3) + version: link:../graphqlsp typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 packages/example-external-generator: dependencies: @@ -124,10 +124,10 @@ importers: devDependencies: '@0no-co/graphqlsp': specifier: file:../graphqlsp - version: file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3) + version: link:../graphqlsp '@graphql-codegen/cli': specifier: ^5.0.0 - version: 5.0.0(@types/node@22.20.0)(enquirer@2.4.1)(graphql@16.8.1)(typescript@5.3.3) + version: 5.0.0(@types/node@22.20.0)(enquirer@2.4.1)(graphql@16.8.1)(typescript@6.0.3) '@graphql-codegen/client-preset': specifier: ^4.1.0 version: 4.1.0(graphql@16.8.1) @@ -136,10 +136,10 @@ importers: version: 18.2.45 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@22.20.0)(typescript@5.3.3) + version: 10.9.1(@types/node@22.20.0)(typescript@6.0.3) typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 packages/example-tada: dependencies: @@ -151,7 +151,7 @@ importers: version: 3.0.0(graphql@16.8.1) gql.tada: specifier: ^1.11.0 - version: 1.11.0(graphql@16.8.1)(typescript@5.3.3) + version: 1.11.0(graphql@16.8.1)(typescript@6.0.3) graphql: specifier: ^16.8.1 version: 16.8.1 @@ -161,10 +161,10 @@ importers: devDependencies: '@0no-co/graphqlsp': specifier: file:../graphqlsp - version: file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3) + version: link:../graphqlsp '@graphql-codegen/cli': specifier: ^5.0.0 - version: 5.0.0(@types/node@22.20.0)(enquirer@2.4.1)(graphql@16.8.1)(typescript@5.3.3) + version: 5.0.0(@types/node@22.20.0)(enquirer@2.4.1)(graphql@16.8.1)(typescript@6.0.3) '@graphql-codegen/client-preset': specifier: ^4.1.0 version: 4.1.0(graphql@16.8.1) @@ -173,16 +173,16 @@ importers: version: 18.2.45 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@22.20.0)(typescript@5.3.3) + version: 10.9.1(@types/node@22.20.0)(typescript@6.0.3) typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 packages/graphqlsp: dependencies: '@gql.tada/internal': specifier: ^1.2.1 - version: 1.2.1(graphql@16.8.1)(typescript@5.9.3) + version: 1.2.1(graphql@16.8.1)(typescript@6.0.3) graphql: specifier: ^15.5.0 || ^16.0.0 || ^17.0.0 version: 16.8.1 @@ -203,8 +203,8 @@ importers: specifier: ^11.5.1 version: 11.5.1 typescript: - specifier: ^5.3.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project: dependencies: @@ -216,8 +216,8 @@ importers: version: 4.0.4(graphql@16.8.1) devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-client-preset: dependencies: @@ -235,8 +235,8 @@ importers: version: 16.8.1 devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-misconfiguration: dependencies: @@ -248,8 +248,8 @@ importers: version: 4.3.0(graphql@16.8.1) devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-missing-schema: dependencies: @@ -261,8 +261,8 @@ importers: version: 4.3.0(graphql@16.8.1) devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-mode-mismatch: dependencies: @@ -274,8 +274,8 @@ importers: version: 4.3.0(graphql@16.8.1) devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-tada: dependencies: @@ -290,14 +290,14 @@ importers: version: 4.0.4(graphql@16.8.1) gql.tada: specifier: ^1.11.0 - version: 1.11.0(graphql@16.8.1)(typescript@5.3.3) + version: 1.11.0(graphql@16.8.1)(typescript@6.0.3) graphql: specifier: ^16.0.0 version: 16.8.1 devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-tada-multi-schema: dependencies: @@ -312,14 +312,14 @@ importers: version: 4.0.4(graphql@16.8.1) gql.tada: specifier: ^1.11.0 - version: 1.11.0(graphql@16.8.1)(typescript@5.3.3) + version: 1.11.0(graphql@16.8.1)(typescript@6.0.3) graphql: specifier: ^16.0.0 version: 16.8.1 devDependencies: typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 test/e2e/fixture-project-unused-fields: dependencies: @@ -343,8 +343,8 @@ importers: specifier: 18.2.45 version: 18.2.45 typescript: - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.0.3 + version: 6.0.3 packages: @@ -368,13 +368,13 @@ packages: resolution: {integrity: sha512-4PPvxDPmbntddpgMyA3VId5/E9YGdRuuS/mW+THOvtTx/C79Pf+lN28LkNNACJrF9L7YACiAJelyOkC6LqUzvw==} peerDependencies: graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 - typescript: ^5.3.3 + typescript: ^6.0.3 '@0no-co/graphqlsp@file:packages/graphqlsp': resolution: {directory: packages/graphqlsp, type: directory} peerDependencies: graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 - typescript: ^5.3.3 + typescript: ^6.0.3 '@actions/core@3.0.1': resolution: {integrity: sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==} @@ -650,7 +650,7 @@ packages: '@gql.tada/svelte-support': 1.0.3 '@gql.tada/vue-support': 1.0.3 graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 - typescript: ^5.3.3 + typescript: ^6.0.3 peerDependenciesMeta: '@gql.tada/svelte-support': optional: true @@ -661,13 +661,13 @@ packages: resolution: {integrity: sha512-mbrWlSLNMCMuP7uGFm4EGFJ0oU1XqzUqykAqQ4emVLUvuERKB6Vhb3KoWdSqSCCBKm7G3WcYCYgFbaMUUMNshw==} peerDependencies: graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 - typescript: ^5.3.3 + typescript: ^6.0.3 '@gql.tada/internal@1.2.1': resolution: {integrity: sha512-1kPMv9KRpD6mfVwtXK+iy43U/gi4bpr4ganfhPLD0TjxpbuVJm7CtZ9wMFdwy3FjLBFN2QhwscNnL7lRPHg4vg==} peerDependencies: graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 - typescript: ^5.3.3 + typescript: ^6.0.3 '@graphql-codegen/add@5.0.3': resolution: {integrity: sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==} @@ -1172,7 +1172,7 @@ packages: peerDependencies: rollup: ^2.14.0||^3.0.0||^4.0.0 tslib: '*' - typescript: ^5.3.3 + typescript: ^6.0.3 peerDependenciesMeta: rollup: optional: true @@ -1685,7 +1685,7 @@ packages: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: - typescript: ^5.3.3 + typescript: ^6.0.3 peerDependenciesMeta: typescript: optional: true @@ -1924,7 +1924,7 @@ packages: resolution: {integrity: sha512-QmBB7HTPBH3fBHdAEBBbq8N/1tyq9abdINply2rSUW1FVvZJ/b/jOD8esKVHh+JEajCCtfjGl0Kx2RyvXJFNCQ==} hasBin: true peerDependencies: - typescript: ^5.3.3 + typescript: ^6.0.3 graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2614,7 +2614,7 @@ packages: engines: {node: '>=20'} peerDependencies: rollup: ^3.29.4 || ^4 - typescript: ^5.3.3 + typescript: ^6.0.3 rollup@4.62.2: resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} @@ -2841,7 +2841,7 @@ packages: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' '@types/node': '*' - typescript: ^5.3.3 + typescript: ^6.0.3 peerDependenciesMeta: '@swc/core': optional: true @@ -2865,13 +2865,8 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -3130,17 +3125,17 @@ snapshots: optionalDependencies: graphql: 16.8.1 - '@0no-co/graphqlsp@1.17.3(graphql@16.8.1)(typescript@5.3.3)': + '@0no-co/graphqlsp@1.17.3(graphql@16.8.1)(typescript@6.0.3)': dependencies: - '@gql.tada/internal': 1.2.0(graphql@16.8.1)(typescript@5.3.3) + '@gql.tada/internal': 1.2.0(graphql@16.8.1)(typescript@6.0.3) graphql: 16.8.1 - typescript: 5.3.3 + typescript: 6.0.3 - '@0no-co/graphqlsp@file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3)': + '@0no-co/graphqlsp@file:packages/graphqlsp(graphql@16.8.1)(typescript@6.0.3)': dependencies: - '@gql.tada/internal': 1.2.1(graphql@16.8.1)(typescript@5.3.3) + '@gql.tada/internal': 1.2.1(graphql@16.8.1)(typescript@6.0.3) graphql: 16.8.1 - typescript: 5.3.3 + typescript: 6.0.3 '@actions/core@3.0.1': dependencies: @@ -3581,30 +3576,24 @@ snapshots: '@fastify/busboy@3.2.0': {} - '@gql.tada/cli-utils@1.9.0(@0no-co/graphqlsp@file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3))(graphql@16.8.1)(typescript@5.3.3)': - dependencies: - '@0no-co/graphqlsp': file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3) - '@gql.tada/internal': 1.2.0(graphql@16.8.1)(typescript@5.3.3) - graphql: 16.8.1 - typescript: 5.3.3 - - '@gql.tada/internal@1.2.0(graphql@16.8.1)(typescript@5.3.3)': + '@gql.tada/cli-utils@1.9.0(@0no-co/graphqlsp@file:packages/graphqlsp(graphql@16.8.1)(typescript@6.0.3))(graphql@16.8.1)(typescript@6.0.3)': dependencies: - '@0no-co/graphql.web': 1.3.2(graphql@16.8.1) + '@0no-co/graphqlsp': file:packages/graphqlsp(graphql@16.8.1)(typescript@6.0.3) + '@gql.tada/internal': 1.2.0(graphql@16.8.1)(typescript@6.0.3) graphql: 16.8.1 - typescript: 5.3.3 + typescript: 6.0.3 - '@gql.tada/internal@1.2.1(graphql@16.8.1)(typescript@5.3.3)': + '@gql.tada/internal@1.2.0(graphql@16.8.1)(typescript@6.0.3)': dependencies: '@0no-co/graphql.web': 1.3.2(graphql@16.8.1) graphql: 16.8.1 - typescript: 5.3.3 + typescript: 6.0.3 - '@gql.tada/internal@1.2.1(graphql@16.8.1)(typescript@5.9.3)': + '@gql.tada/internal@1.2.1(graphql@16.8.1)(typescript@6.0.3)': dependencies: '@0no-co/graphql.web': 1.3.2(graphql@16.8.1) graphql: 16.8.1 - typescript: 5.9.3 + typescript: 6.0.3 '@graphql-codegen/add@5.0.3(graphql@16.8.1)': dependencies: @@ -3612,7 +3601,7 @@ snapshots: graphql: 16.8.1 tslib: 2.6.3 - '@graphql-codegen/cli@5.0.0(@types/node@22.20.0)(enquirer@2.4.1)(graphql@16.8.1)(typescript@5.3.3)': + '@graphql-codegen/cli@5.0.0(@types/node@22.20.0)(enquirer@2.4.1)(graphql@16.8.1)(typescript@6.0.3)': dependencies: '@babel/generator': 7.28.5 '@babel/template': 7.27.2 @@ -3631,11 +3620,11 @@ snapshots: '@graphql-tools/utils': 10.10.1(graphql@16.8.1) '@whatwg-node/fetch': 0.8.8 chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.3.3) + cosmiconfig: 8.3.6(typescript@6.0.3) debounce: 1.2.1 detect-indent: 6.1.0 graphql: 16.8.1 - graphql-config: 5.1.5(@types/node@22.20.0)(graphql@16.8.1)(typescript@5.3.3) + graphql-config: 5.1.5(@types/node@22.20.0)(graphql@16.8.1)(typescript@6.0.3) inquirer: 8.2.7(@types/node@22.20.0) is-glob: 4.0.3 jiti: 1.21.7 @@ -4326,11 +4315,11 @@ snapshots: optionalDependencies: rollup: 4.62.2 - '@rollup/plugin-typescript@12.3.0(rollup@4.62.2)(tslib@2.8.1)(typescript@5.9.3)': + '@rollup/plugin-typescript@12.3.0(rollup@4.62.2)(tslib@2.8.1)(typescript@6.0.3)': dependencies: '@rollup/pluginutils': 5.4.0(rollup@4.62.2) resolve: 1.22.12 - typescript: 5.9.3 + typescript: 6.0.3 optionalDependencies: rollup: 4.62.2 tslib: 2.8.1 @@ -4800,14 +4789,14 @@ snapshots: convert-source-map@2.0.0: {} - cosmiconfig@8.3.6(typescript@5.3.3): + cosmiconfig@8.3.6(typescript@6.0.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.3.3 + typescript: 6.0.3 create-require@1.1.1: {} @@ -5013,13 +5002,13 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - gql.tada@1.11.0(graphql@16.8.1)(typescript@5.3.3): + gql.tada@1.11.0(graphql@16.8.1)(typescript@6.0.3): dependencies: '@0no-co/graphql.web': 1.3.2(graphql@16.8.1) - '@0no-co/graphqlsp': 1.17.3(graphql@16.8.1)(typescript@5.3.3) - '@gql.tada/cli-utils': 1.9.0(@0no-co/graphqlsp@file:packages/graphqlsp(graphql@16.8.1)(typescript@5.3.3))(graphql@16.8.1)(typescript@5.3.3) - '@gql.tada/internal': 1.2.0(graphql@16.8.1)(typescript@5.3.3) - typescript: 5.3.3 + '@0no-co/graphqlsp': 1.17.3(graphql@16.8.1)(typescript@6.0.3) + '@gql.tada/cli-utils': 1.9.0(@0no-co/graphqlsp@file:packages/graphqlsp(graphql@16.8.1)(typescript@6.0.3))(graphql@16.8.1)(typescript@6.0.3) + '@gql.tada/internal': 1.2.0(graphql@16.8.1)(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - '@gql.tada/svelte-support' - '@gql.tada/vue-support' @@ -5027,7 +5016,7 @@ snapshots: graceful-fs@4.2.11: {} - graphql-config@5.1.5(@types/node@22.20.0)(graphql@16.8.1)(typescript@5.3.3): + graphql-config@5.1.5(@types/node@22.20.0)(graphql@16.8.1)(typescript@6.0.3): dependencies: '@graphql-tools/graphql-file-loader': 8.1.4(graphql@16.8.1) '@graphql-tools/json-file-loader': 8.0.22(graphql@16.8.1) @@ -5035,7 +5024,7 @@ snapshots: '@graphql-tools/merge': 9.1.3(graphql@16.8.1) '@graphql-tools/url-loader': 8.0.33(@types/node@22.20.0)(graphql@16.8.1) '@graphql-tools/utils': 10.10.1(graphql@16.8.1) - cosmiconfig: 8.3.6(typescript@5.3.3) + cosmiconfig: 8.3.6(typescript@6.0.3) graphql: 16.8.1 jiti: 2.6.1 minimatch: 9.0.5 @@ -5664,14 +5653,14 @@ snapshots: cjs-module-lexer: 1.4.3 rollup: 4.62.2 - rollup-plugin-dts@6.4.1(rollup@4.62.2)(typescript@5.9.3): + rollup-plugin-dts@6.4.1(rollup@4.62.2)(typescript@6.0.3): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 convert-source-map: 2.0.0 magic-string: 0.30.21 rollup: 4.62.2 - typescript: 5.9.3 + typescript: 6.0.3 optionalDependencies: '@babel/code-frame': 7.29.7 @@ -5896,7 +5885,7 @@ snapshots: ts-log@2.2.7: {} - ts-node@10.9.1(@types/node@22.20.0)(typescript@5.3.3): + ts-node@10.9.1(@types/node@22.20.0)(typescript@6.0.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -5910,7 +5899,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 6.0.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -5924,9 +5913,7 @@ snapshots: type-fest@0.21.3: {} - typescript@5.3.3: {} - - typescript@5.9.3: {} + typescript@6.0.3: {} ua-parser-js@1.0.41: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 03d44dc9..35d0b1da 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,7 +11,7 @@ minimumReleaseAgeExclude: - 'gql.tada' overrides: - typescript: ^5.3.3 + typescript: ^6.0.3 ua-parser-js@<0.7.33: '>=0.7.33' postcss@<8.4.31: '>=8.4.31' semver@<5.7.2: '>=5.7.2' diff --git a/test/e2e/client-preset.test.ts b/test/e2e/client-preset.test.ts index 781e843d..0057d3bf 100644 --- a/test/e2e/client-preset.test.ts +++ b/test/e2e/client-preset.test.ts @@ -1,5 +1,6 @@ import { expect, afterAll, beforeAll, it, describe } from 'vitest'; import { TSServer } from './server'; +import { pollDiagnostics } from './util'; import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; @@ -118,16 +119,8 @@ describe('Fragment + operations', () => { }); 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(` + const diagnostics = await pollDiagnostics(server, outfileCombo); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -446,20 +439,8 @@ List out all Pokémon, optionally in pages` 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(` + const diagnostics = await pollDiagnostics(server, outfileUnusedFragment); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", diff --git a/test/e2e/combinations.test.ts b/test/e2e/combinations.test.ts index 66dcd203..4d063f48 100644 --- a/test/e2e/combinations.test.ts +++ b/test/e2e/combinations.test.ts @@ -1,5 +1,6 @@ import { expect, afterAll, beforeAll, it, describe } from 'vitest'; import { TSServer } from './server'; +import { pollDiagnostics } from './util'; import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; @@ -46,33 +47,7 @@ describe('Fragment + operations', () => { }); it('gives semantic-diagnostics with preceding fragments', async () => { - // The schema is loaded asynchronously, so the automatically emitted - // diagnostics may predate it; poll with "geterr" until the plugin - // reports GraphQL diagnostics for the document. - let diagnostics: any[] = []; - for (let attempt = 0; attempt < 40; attempt++) { - const seen = server.responses.length; - server.sendCommand('geterr', { files: [outfileCombinations], delay: 0 }); - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileCombinations - ); - const res = server.responses - .slice(seen) - .find( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileCombinations - ) as any; - if (res && res.body.diagnostics.length) { - diagnostics = res.body.diagnostics; - break; - } - await new Promise(resolve => setTimeout(resolve, 250)); - } + const diagnostics = await pollDiagnostics(server, outfileCombinations); expect(diagnostics).toMatchInlineSnapshot(` [ { diff --git a/test/e2e/misconfiguration.test.ts b/test/e2e/misconfiguration.test.ts index 79ae6204..fa7a0353 100644 --- a/test/e2e/misconfiguration.test.ts +++ b/test/e2e/misconfiguration.test.ts @@ -4,39 +4,10 @@ import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; import ts from 'typescript/lib/tsserverlibrary'; +import { pollDiagnostics as waitForDiagnostics } from './util'; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); -// Polls the server with "geterr" until a non-empty semantic-diagnostics -// response arrives for the given file. The schema is loaded asynchronously, -// so the error-state may not have settled when the file is first checked. -const waitForDiagnostics = async ( - server: TSServer, - file: string -): Promise => { - for (let attempt = 0; attempt < 20; attempt++) { - const seen = server.responses.length; - server.sendCommand('geterr', { files: [file], delay: 0 }); - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === file - ); - const res = server.responses - .slice(seen) - .find( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === file - ) as any; - if (res && res.body.diagnostics.length) return res.body.diagnostics; - await new Promise(resolve => setTimeout(resolve, 250)); - } - return []; -}; - const openFixture = async (server: TSServer, projectPath: string) => { const testFile = path.join(projectPath, 'simple.ts'); const fixtureFileContent = fs.readFileSync( diff --git a/test/e2e/multi-schema-tada.test.ts b/test/e2e/multi-schema-tada.test.ts index 8dab39fb..885d3762 100644 --- a/test/e2e/multi-schema-tada.test.ts +++ b/test/e2e/multi-schema-tada.test.ts @@ -1,5 +1,6 @@ import { expect, afterAll, beforeAll, it, describe } from 'vitest'; import { TSServer } from './server'; +import { pollDiagnostics } from './util'; import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; @@ -88,20 +89,10 @@ describe('Multiple schemas', () => { }); 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 - ); + const diagnostics = await pollDiagnostics(server, outfilePokemonTest); - expect(res).toBeDefined(); - expect(res).toHaveLength(1); - expect(res[0].body.diagnostics).toHaveLength(1); - expect(res[0].body.diagnostics[0]).toMatchInlineSnapshot(` + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toMatchInlineSnapshot(` { "category": "warning", "code": 52004, diff --git a/test/e2e/persisted-tada.test.ts b/test/e2e/persisted-tada.test.ts index c3992bf4..59484da8 100644 --- a/test/e2e/persisted-tada.test.ts +++ b/test/e2e/persisted-tada.test.ts @@ -1,6 +1,7 @@ import { parse, print } from '@0no-co/graphql.web'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { TSServer } from './server'; +import { pollDiagnostics } from './util'; import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; @@ -52,17 +53,9 @@ describe('Persisted operation hash + tada', () => { }); it('reports a hash mismatch when a fragment referenced via nested property access (Component.fragments.) is included in the document', async () => { - await server.waitForResponse( - e => e.type === 'event' && e.event === 'semanticDiag' - ); - const responses = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfile + const lastDiagnostics = await pollDiagnostics(server, outfile, d => + d.some(x => x.code === MISSMATCH_HASH_TO_DOCUMENT) ); - const lastDiagnostics: any[] = - responses[responses.length - 1]?.body?.diagnostics ?? []; const hashMismatch = lastDiagnostics.find( d => d.code === MISSMATCH_HASH_TO_DOCUMENT diff --git a/test/e2e/tada.test.ts b/test/e2e/tada.test.ts index 26f891e1..e9c2508d 100644 --- a/test/e2e/tada.test.ts +++ b/test/e2e/tada.test.ts @@ -1,5 +1,6 @@ import { expect, afterAll, beforeAll, it, describe } from 'vitest'; import { TSServer } from './server'; +import { pollDiagnostics } from './util'; import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; @@ -142,16 +143,8 @@ describe('Fragment + operations', () => { }); 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(` + const diagnostics = await pollDiagnostics(server, outfileCombo); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -470,20 +463,8 @@ List out all Pokémon, optionally in pages` 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(` + const diagnostics = await pollDiagnostics(server, outfileUnusedFragment); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -508,21 +489,16 @@ List out all Pokémon, optionally in pages` tmpfile: outfileUsedFragmentMask, } satisfies ts.server.protocol.SavetoRequestArgs); - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileUsedFragmentMask - ); - - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileUsedFragmentMask + // A single "geterr" round suffices: the schema is known to be loaded + // once the preceding tests have seen GraphQL diagnostics + const diagnostics = await pollDiagnostics( + server, + outfileUsedFragmentMask, + () => true, + 1 ); // Should have no diagnostics about unused fragments since maskFragments uses them - expect(res[0].body.diagnostics).toMatchInlineSnapshot(`[]`); + expect(diagnostics).toMatchInlineSnapshot(`[]`); }, 30000); it('should not warn about unused fragments when the fragment is used directly (not co-located)', async () => { @@ -531,20 +507,13 @@ List out all Pokémon, optionally in pages` tmpfile: outfileUsedFragmentDirect, } satisfies ts.server.protocol.SavetoRequestArgs); - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileUsedFragmentDirect - ); - - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileUsedFragmentDirect + const diagnostics = await pollDiagnostics( + server, + outfileUsedFragmentDirect, + () => true, + 1 ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(`[]`); + expect(diagnostics).toMatchInlineSnapshot(`[]`); }, 30000); it('gives quick-info at start of word (#15)', async () => { @@ -899,25 +868,15 @@ describe('Fragment dependencies - Issue #494', () => { }); it('warns about missing fragment dep even when fragment is used in another query in same file', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileMissingFragmentDep - ); - - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileMissingFragmentDep + const diagnostics = await pollDiagnostics( + server, + outfileMissingFragmentDep ); // Should have a diagnostic about the unknown fragment in SecondQuery - expect(res.length).toBeGreaterThan(0); - expect(res[0].body.diagnostics.length).toBeGreaterThan(0); + expect(diagnostics.length).toBeGreaterThan(0); - const fragmentError = res[0].body.diagnostics.find((diag: any) => + const fragmentError = diagnostics.find((diag: any) => diag.text.includes('PokemonBasicInfo') ); diff --git a/test/e2e/unused-fieds.test.ts b/test/e2e/unused-fieds.test.ts index 8a67696d..65958d41 100644 --- a/test/e2e/unused-fieds.test.ts +++ b/test/e2e/unused-fieds.test.ts @@ -1,5 +1,6 @@ import { expect, afterAll, beforeAll, it, describe } from 'vitest'; import { TSServer } from './server'; +import { pollDiagnostics } from './util'; import path from 'node:path'; import fs from 'node:fs'; import url from 'node:url'; @@ -178,20 +179,8 @@ describe('unused fields', () => { }); it('gives unused fields with fragments', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileFragment, - true - ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileFragment - ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(` + const diagnostics = await pollDiagnostics(server, outfileFragment); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -211,20 +200,11 @@ describe('unused fields', () => { }, 30000); it('gives unused fields with fragments destructuring', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileFragmentDestructuring, - true + const diagnostics = await pollDiagnostics( + server, + outfileFragmentDestructuring ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileFragmentDestructuring - ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(` + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -244,20 +224,8 @@ describe('unused fields', () => { }, 30000); it('gives semantc diagnostics with property access', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfilePropAccess, - true - ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfilePropAccess - ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(` + const diagnostics = await pollDiagnostics(server, outfilePropAccess); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -316,20 +284,8 @@ describe('unused fields', () => { }, 30000); it('gives unused fields with destructuring', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileDestructuring, - true - ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileDestructuring - ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(` + const diagnostics = await pollDiagnostics(server, outfileDestructuring); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -375,20 +331,11 @@ describe('unused fields', () => { }, 30000); it('gives unused fields with immedaite destructuring', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileDestructuringFromStart, - true + const diagnostics = await pollDiagnostics( + server, + outfileDestructuringFromStart ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileDestructuringFromStart - ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(` + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "warning", @@ -434,20 +381,8 @@ describe('unused fields', () => { }, 30000); it('Bails unused fields when memo func is used', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileBail, - true - ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileBail - ); - expect(res[0].body.diagnostics).toMatchInlineSnapshot(` + const diagnostics = await pollDiagnostics(server, outfileBail); + expect(diagnostics).toMatchInlineSnapshot(` [ { "category": "error", @@ -467,22 +402,10 @@ describe('unused fields', () => { }, 30000); it('Tracks multiple documents, alias chains and named callbacks in one file', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileMultiDocument, - true - ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileMultiDocument - ); + const diagnostics = await pollDiagnostics(server, outfileMultiDocument); // The Pok document has no generated type (same as chained-usage.ts), so // we only assert the unused-field diagnostics here. - const unusedFieldDiagnostics = res[0].body.diagnostics.filter( + const unusedFieldDiagnostics = diagnostics.filter( (diagnostic: any) => diagnostic.code === 52005 ); expect(unusedFieldDiagnostics).toMatchInlineSnapshot(` @@ -583,20 +506,8 @@ describe('unused fields', () => { }, 30000); it('Finds field usage in chained call-expressions', async () => { - await server.waitForResponse( - e => - e.type === 'event' && - e.event === 'semanticDiag' && - e.body?.file === outfileChainedUsage, - true - ); - const res = server.responses.filter( - resp => - resp.type === 'event' && - resp.event === 'semanticDiag' && - resp.body?.file === outfileChainedUsage - ); - expect(res[0].body.diagnostics[0]).toEqual({ + const diagnostics = await pollDiagnostics(server, outfileChainedUsage); + expect(diagnostics[0]).toEqual({ category: 'warning', code: 52005, end: { diff --git a/test/e2e/util.ts b/test/e2e/util.ts index 7f9724b2..0c37a21e 100644 --- a/test/e2e/util.ts +++ b/test/e2e/util.ts @@ -1,10 +1,49 @@ import { vi } from 'vitest'; +import type { TSServer } from './server'; type WaitForExpectOptions = { timeout?: number; interval?: number; }; +/** Polls the server with "geterr" until the semantic diagnostics for `file` + * satisfy `isReady`, and returns the last received diagnostics. + * + * The plugin loads schemas asynchronously, so the diagnostics tsserver emits + * automatically after a file is opened may predate the schema and come back + * empty; asserting on the first received event races the schema load. */ +export const pollDiagnostics = async ( + server: TSServer, + file: string, + isReady: (diagnostics: any[]) => boolean = diagnostics => + diagnostics.length > 0, + attempts = 40 +): Promise => { + let diagnostics: any[] = []; + for (let attempt = 0; attempt < attempts; attempt++) { + const seen = server.responses.length; + server.sendCommand('geterr', { files: [file], delay: 0 }); + await server.waitForResponse( + e => + e.type === 'event' && + e.event === 'semanticDiag' && + (e as any).body?.file === file + ); + const res = server.responses + .slice(seen) + .find( + e => + e.type === 'event' && + e.event === 'semanticDiag' && + (e as any).body?.file === file + ) as any; + if (res) diagnostics = res.body.diagnostics; + if (res && isReady(diagnostics)) break; + await new Promise(resolve => setTimeout(resolve, 250)); + } + return diagnostics; +}; + export const waitForExpect = async ( expectFn: () => void, { interval = 2000, timeout = 30000 }: WaitForExpectOptions = {} From 18cb6514be688eea62150de23c4f4b7b420f1777 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sun, 5 Jul 2026 16:56:56 +0200 Subject: [PATCH 2/2] test: only treat settled GraphQL diagnostics as poll-ready On CI, module resolution settles slower than locally, and the first geterr rounds report transient TypeScript errors (2307 for imports of plugin-generated files) that disappear once project loading finishes. pollDiagnostics treated any non-empty diagnostics as ready, so the client-preset suite asserted on that transient state and failed. Require the settled state instead: at least one diagnostic and none below the plugin's 52001+ code range. Sites whose settled diagnostics legitimately include TypeScript errors (ts-expect-error fixture, documents without generated types) pass an explicit readiness check. Co-Authored-By: Claude Fable 5 --- test/e2e/tada.test.ts | 11 +++++------ test/e2e/unused-fieds.test.ts | 18 +++++++++++++++--- test/e2e/util.ts | 11 +++++++++-- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/test/e2e/tada.test.ts b/test/e2e/tada.test.ts index e9c2508d..e235d953 100644 --- a/test/e2e/tada.test.ts +++ b/test/e2e/tada.test.ts @@ -489,13 +489,13 @@ List out all Pokémon, optionally in pages` tmpfile: outfileUsedFragmentMask, } satisfies ts.server.protocol.SavetoRequestArgs); - // A single "geterr" round suffices: the schema is known to be loaded - // once the preceding tests have seen GraphQL diagnostics + // The settled state is empty, so only wait out transient TypeScript + // diagnostics; the schema is known to be loaded once the preceding + // tests have seen GraphQL diagnostics const diagnostics = await pollDiagnostics( server, outfileUsedFragmentMask, - () => true, - 1 + d => d.every(x => x.code >= 52000) ); // Should have no diagnostics about unused fragments since maskFragments uses them expect(diagnostics).toMatchInlineSnapshot(`[]`); @@ -510,8 +510,7 @@ List out all Pokémon, optionally in pages` const diagnostics = await pollDiagnostics( server, outfileUsedFragmentDirect, - () => true, - 1 + d => d.every(x => x.code >= 52000) ); expect(diagnostics).toMatchInlineSnapshot(`[]`); }, 30000); diff --git a/test/e2e/unused-fieds.test.ts b/test/e2e/unused-fieds.test.ts index 65958d41..f068ec4f 100644 --- a/test/e2e/unused-fieds.test.ts +++ b/test/e2e/unused-fieds.test.ts @@ -381,7 +381,11 @@ describe('unused fields', () => { }, 30000); it('Bails unused fields when memo func is used', async () => { - const diagnostics = await pollDiagnostics(server, outfileBail); + // The settled state for this file is a TypeScript diagnostic, so the + // default all-GraphQL readiness check doesn't apply + const diagnostics = await pollDiagnostics(server, outfileBail, d => + d.some(x => x.code === 2578) + ); expect(diagnostics).toMatchInlineSnapshot(` [ { @@ -402,7 +406,11 @@ describe('unused fields', () => { }, 30000); it('Tracks multiple documents, alias chains and named callbacks in one file', async () => { - const diagnostics = await pollDiagnostics(server, outfileMultiDocument); + // The Pok document has no generated type, so TypeScript diagnostics + // remain in the settled state; wait for the unused-field warnings + const diagnostics = await pollDiagnostics(server, outfileMultiDocument, d => + d.some(x => x.code === 52005) + ); // The Pok document has no generated type (same as chained-usage.ts), so // we only assert the unused-field diagnostics here. const unusedFieldDiagnostics = diagnostics.filter( @@ -506,7 +514,11 @@ describe('unused fields', () => { }, 30000); it('Finds field usage in chained call-expressions', async () => { - const diagnostics = await pollDiagnostics(server, outfileChainedUsage); + // The chained document has no generated type, so TypeScript diagnostics + // remain in the settled state; wait for the unused-field warning + const diagnostics = await pollDiagnostics(server, outfileChainedUsage, d => + d.some(x => x.code === 52005) + ); expect(diagnostics[0]).toEqual({ category: 'warning', code: 52005, diff --git a/test/e2e/util.ts b/test/e2e/util.ts index 0c37a21e..56643b26 100644 --- a/test/e2e/util.ts +++ b/test/e2e/util.ts @@ -11,12 +11,19 @@ type WaitForExpectOptions = { * * The plugin loads schemas asynchronously, so the diagnostics tsserver emits * automatically after a file is opened may predate the schema and come back - * empty; asserting on the first received event races the schema load. */ + * empty; asserting on the first received event races the schema load. + * + * The default readiness check requires at least one diagnostic and no + * regular TypeScript diagnostics: while the project is still resolving + * modules, tsserver reports transient errors (e.g. 2307 for imports of + * generated files) that disappear once loading settles, and the plugin's + * own diagnostics all use codes from 52001 upwards. Pass a custom check + * for files whose settled state includes TypeScript diagnostics. */ export const pollDiagnostics = async ( server: TSServer, file: string, isReady: (diagnostics: any[]) => boolean = diagnostics => - diagnostics.length > 0, + diagnostics.length > 0 && diagnostics.every(d => d.code >= 52000), attempts = 40 ): Promise => { let diagnostics: any[] = [];