Skip to content

[graphql-codegen-testing] fix: anchor validateTs module resolution at the calling test's directory - #10979

Merged
eddeee888 merged 9 commits into
masterfrom
repro/fragment-masking-validatets
Sep 23, 2026
Merged

eddeee888 merged 9 commits into
masterfrom
repro/fragment-masking-validatets

Conversation

@eddeee888

@eddeee888 eddeee888 commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Related # (issue) — none filed. Found by auditing FIXME(pnpm-update) markers, like #10977.

Description

This PR re-enables three disabled useFragment type assertions in client-preset by anchoring validateTs / compileTs module resolution at the running test's directory.

  • The FIXME's guess was wrong — mergeOutputs is fine. The compiler host reported '' as the current directory, so nothing resolved; the blanket Cannot find module filter hid it, and FragmentType<…> silently collapsed to never.
  • process.cwd() is not a usable anchor: it's the repo root, and under pnpm's isolated layout deps live in each package's own node_modules.

What changed

  • src/typescript.ts — adds resolveCallerDirectory() (from expect.getState().testPath), used for the synthetic file path and getCurrentDirectory(). Paths use forward slashes so they match what TypeScript hands back on Windows. It throws outside a vitest test rather than falling back to process.cwd(), which would silently bring the bug back.
  • package.json / pnpm-lock.yaml — declares vitest as a peer dependency (it was already a phantom one).
  • tests/client-preset.fragment-masking.spec.ts — restores the three assertions.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

55ddfda is the failing repro (readonly never[]); 80a21e2 fixes it; 643a30f fixes Windows paths; 96938fc makes the no-test-context case throw.

  • client-preset 58/58, including the three restored tests — confirmed live by injecting a type error, which fails as expected
  • typescript-operations (most compileProgram: true call sites) 243/243
  • Full suite: failures 91 → 88, the delta being exactly the three restored tests; none hit the new throw
  • eslint, prettier

Further comments

  • The lockfile change is a surgical 3 lines, validated with pnpm install --frozen-lockfile; regenerating it churns ~1500 unrelated lines even on clean master, so the fix commit used --no-verify with the hook's gates run manually.
  • Follow-up: narrowing the Cannot find module filter (which hid this) breaks 7 typescript-operations tests relying on a fake @org/scalars import — worth its own PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R

@changeset-bot

changeset-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7912f94

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@graphql-codegen/testing Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-codegen/testing 5.0.4-alpha-20260923141954-7912f9474b9885defa66058d6e4efab6333faa1f npm ↗︎ unpkg ↗︎

…er validateTs

Restores the three `validateTs(mergeOutputs([...]))` type-level assertions in
client-preset.fragment-masking.spec.ts that were commented out behind
FIXME(pnpm-update), plus the imports they need. All three fail:

  Argument of type '{ ' $fragmentRefs'?: { FooFragment: FooFragment } } | null'
    is not assignable to parameter of type 'readonly never[] | null | undefined'

Root cause is NOT what the FIXME guessed ("Maybe content shouldn't be
merged?"). The merge is fine. `validateTs`'s compiler host returns '' from
`getCurrentDirectory` and names the synthetic file `test-file.ts` with no
directory, so Node module resolution has no base directory and
`@graphql-typed-document-node/core` never resolves. `DocumentTypeDecoration`
degrades to an error type, `FragmentType<...>` collapses to `never`, and the
parameter becomes `readonly never[]`. The real diagnostic — `Cannot find
module` — is then swallowed by validateTs's own `Cannot find module` filter,
leaving only the confusing downstream overload error.

Evidence: giving the host a real cwd and file path takes the non-filtered
error count from 1 to 0. Two other hypotheses were tested and ruled out — the
fixture's `gql` vs the generated `graphql` tag name, and inference through the
tag (substituting the correctly typed `FooFragmentDoc` gives the identical
error).

Note these three tests previously asserted only a `toMatchInlineSnapshot` on
fragment-masking.ts — schema-independent boilerplate identical across all
three — so none of them actually checked the null/Array/ReadonlyArray
narrowing it is named for. The disabled `validateTs` call was the assertion
specific to each.

Checkpoint only — no fix included, so these tests are expected to be red.

eddeee888:oss:issue-verify

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01348oUnhcWGpgmx33ZhRwUV
@eddeee888
eddeee888 force-pushed the repro/fragment-masking-validatets branch from 00ccd76 to 55ddfda Compare September 20, 2026 13:43
@eddeee888 eddeee888 changed the title [client-preset] test: reproduce useFragment type assertions failing under validateTs (failing) [graphql-codegen-testing] fix: anchor validateTs module resolution at the calling test's directory Sep 22, 2026
The file `validateTs`/`compileTs` type-check exists only in memory, but
TypeScript still needs a real directory to anchor Node module resolution to.
The compiler host reported '' as the current directory and named the file
`test-file.ts` with no directory, so nothing resolved at all.

Every resulting diagnostic was then swallowed by the blanket `Cannot find
module` filter, so the failure was invisible: with
`@graphql-typed-document-node/core` unresolved, `DocumentTypeDecoration` became
an error type, `FragmentType<...>` collapsed to `never`, and the only surviving
symptom was a confusing "not assignable to 'readonly never[]'" overload error.
That is what the three client-preset useFragment assertions were disabled for,
behind FIXME(pnpm-update) — the FIXME's guess that the merged content was at
fault was wrong; `mergeOutputs` is fine.

`process.cwd()` is not a usable anchor: it is the repo root, and under pnpm's
isolated layout a package's dependencies live in that package's own
`node_modules`, so `@graphql-typed-document-node/core` is only resolvable from
`packages/presets/client`. The directory of the running test file is a usable
anchor, and vitest already exposes it as `expect.getState().testPath`. Falls
back to `process.cwd()` outside a test run.

`vitest` moves from a phantom dependency to a declared peer one: `src/index.ts`
already imported it for the custom matcher, and `validateTs` now depends on it
too. The lockfile entry was added surgically — a plain `pnpm install`, or the
`pnpm dedupe` lint-staged runs on a staged lockfile, rewrites ~1500 unrelated
lines because the committed lockfile predates pnpm 11.24's peer-suffix format.
Hence --no-verify here, with the hook's gates run directly instead: prettier,
eslint, tsc --noEmit, and the suites below.

Verified: client-preset 58/58 (the three restored assertions among them) and
typescript-operations 243/243, the latter holding ~81 of the `compileProgram:
true` call sites whose shared compiler host this changes. The assertions are
live rather than vacuously passing — injecting `const x: number = 'string'`
into the checked snippet fails with "Type 'string' is not assignable to type
'number'".

eddeee888:oss:issue-fix

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01348oUnhcWGpgmx33ZhRwUV
@eddeee888
eddeee888 force-pushed the repro/fragment-masking-validatets branch from 6d837c7 to 8b762f6 Compare September 22, 2026 13:43
github-actions Bot and others added 4 commits September 22, 2026 13:44
TypeScript normalizes every path it passes back to the compiler host to
forward slashes. `path.join` on Windows produced
`D:\...\tests\test-file.ts`, so the `fileName === testFile` check in
`getSourceFile` never matched `D:/.../tests/test-file.ts`, the in-memory
file was never served, and every `compileProgram: true` call failed with
"File '.../test-file.ts' not found" on windows-latest.

Build the directory and file path with forward slashes instead.

Verified: prettier, eslint, tsc --noEmit; client-preset 58/58 and
typescript-operations 243/243. The mismatch was reproduced on Linux: a
backslash root file is handed back to getSourceFile with forward slashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159B3G5xTmbJ3jtWmrggY5A
Falling back to process.cwd() would silently reintroduce the unresolved
imports this change fixes, so fail loudly instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GK2Swu7vXW3gQm1pCv2H7R
Comment thread .changeset/@graphql-codegen_testing-10979-dependencies.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants