diff --git a/.changeset/green-walls-battle.md b/.changeset/green-walls-battle.md new file mode 100644 index 0000000000..0141ee6a90 --- /dev/null +++ b/.changeset/green-walls-battle.md @@ -0,0 +1,5 @@ +--- +'houdini': patch +--- + +Fix syntax error when generating artifacts for queries that contain fragments with direct inline fragment children diff --git a/packages/houdini/src/codegen/generators/artifacts/selection.test.ts b/packages/houdini/src/codegen/generators/artifacts/selection.test.ts new file mode 100644 index 0000000000..ad6c31b16f --- /dev/null +++ b/packages/houdini/src/codegen/generators/artifacts/selection.test.ts @@ -0,0 +1,89 @@ +import * as graphql from 'graphql' +import { test, expect } from 'vitest' + +import { mockCollectedDoc, testConfig } from '../../../test' +import { flattenSelections } from '../../utils' +import selection from './selection' + +test('fragments of unions inject correctly', function () { + const document = graphql.parse(` + query { + entities { + ...EntityInfo + } + } + + fragment EntityInfo on Entity { + ... on User { + firstName + } + ... on Cat { + name + } + } + `) + + const config = testConfig() + const fragmentDefinitions = { + EntityInfo: document.definitions.find( + (def): def is graphql.FragmentDefinitionNode => def.kind === 'FragmentDefinition' + )!, + } + + const flat = flattenSelections({ + config, + filepath: '', + selections: document.definitions.find( + (def): def is graphql.OperationDefinitionNode => def.kind === 'OperationDefinition' + )!.selectionSet.selections, + fragmentDefinitions, + ignoreMaskDisable: true, + applyFragments: true, + }) + + const artifactSelection = selection({ + config, + filepath: '', + rootType: 'Query', + operations: {}, + selections: flat, + includeFragments: false, + document: mockCollectedDoc(` + query Query { + entities { + ...EntityInfo + } + }`), + }) + + expect(artifactSelection).toMatchInlineSnapshot(` + { + "fields": { + "entities": { + "type": "Entity", + "keyRaw": "entities", + "selection": { + "abstractFields": { + "fields": { + "User": { + "firstName": { + "type": "String", + "keyRaw": "firstName" + } + }, + "Cat": { + "name": { + "type": "String", + "keyRaw": "name" + } + } + }, + "typeMap": {} + } + }, + "abstract": true + } + } + } + `) +}) diff --git a/packages/houdini/src/codegen/utils/flattenSelections.ts b/packages/houdini/src/codegen/utils/flattenSelections.ts index 540988fd15..19f73975a1 100644 --- a/packages/houdini/src/codegen/utils/flattenSelections.ts +++ b/packages/houdini/src/codegen/utils/flattenSelections.ts @@ -66,6 +66,14 @@ class FieldCollection { } } + get size() { + return ( + Object.keys(this.fields).length + + Object.keys(this.inlineFragments).length + + Object.keys(this.fragmentSpreads).length + ) + } + add(selection: graphql.SelectionNode) { // how we handle the field depends on what kind of field it is if (selection.kind === 'Field') { @@ -153,7 +161,6 @@ class FieldCollection { } // instead of adding the field on directly, let's turn the external fragment into an inline fragment - this.add({ kind: 'InlineFragment', typeCondition: { @@ -173,10 +180,17 @@ class FieldCollection { toSelectionSet(): graphql.SelectionNode[] { return Object.values(this.inlineFragments) - .map((fragment) => { + .flatMap((fragment) => { + // if there are no selections in the fragment, skip it + if (fragment.selection.size === 0) { + return [] + } + + // convert the selection to a real selection set fragment.astNode.selectionSet.selections = fragment.selection.toSelectionSet() - return fragment.astNode + // return the value + return [fragment.astNode] }) .concat( Object.values(this.fields).map((field) => {