From 2e7a6d26892d4428ba1afa5a9c171a977f459681 Mon Sep 17 00:00:00 2001 From: vasilich Date: Tue, 11 May 2021 19:56:04 +0300 Subject: [PATCH] bugfix/multiple_queries_in_document --- CHANGELOG.md | 4 + lib/generator.dart | 36 +- pubspec.yaml | 6 +- .../fragment_multiple_queries_test.dart | 419 ++++++++++++++++++ .../multiple_operations_per_file_test.dart | 91 ---- 5 files changed, 443 insertions(+), 113 deletions(-) create mode 100644 test/query_generator/fragments/fragment_multiple_queries_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 043bd87b..d6c450c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 7.0.0-beta.11 + +- document generation fix for https://github.com/comigor/artemis/issues/307 + ## 7.0.0-beta.10 - union generation fix for https://github.com/comigor/artemis/issues/284 diff --git a/lib/generator.dart b/lib/generator.dart index d07f74f0..a081d230 100644 --- a/lib/generator.dart +++ b/lib/generator.dart @@ -127,8 +127,6 @@ Iterable generateDefinitions( List fragmentsCommon, CanonicalVisitor canonicalVisitor, ) { - final fragments = []; - final documentFragments = document.definitions.whereType(); @@ -140,12 +138,19 @@ Iterable generateDefinitions( document.definitions.whereType().toList(); return operations.map((operation) { + final fragments = []; + final definitions = document.definitions + // filtering unused operations + .where((e) { + return e is! OperationDefinitionNode || e == operation; + }).toList(); + if (fragmentsCommon.isEmpty) { fragments.addAll(documentFragments); } else { final fragmentsOperation = _extractFragments(operation.selectionSet, fragmentsCommon); - document.definitions.addAll(fragmentsOperation); + definitions.addAll(fragmentsOperation); fragments.addAll(fragmentsOperation); } @@ -174,14 +179,14 @@ Iterable generateDefinitions( final rootTypeName = (schemaVisitor.schemaDefinitionNode?.operationTypes ?? []) - .firstWhereOrNull((e) => e.operation == operation.type) - ?.type - .name - .value ?? + .firstWhereOrNull((e) => e.operation == operation.type) + ?.type + .name + .value ?? suffix; final TypeDefinitionNode parentType = - objectVisitor.getByName(rootTypeName)!; + objectVisitor.getByName(rootTypeName)!; final name = QueryName.fromPath( path: createPathName([ @@ -208,21 +213,14 @@ Iterable generateDefinitions( usedInputObjects: {}, ); - final visitor = GeneratorVisitor( - context: context, - ); - - DocumentNode( - definitions: document.definitions - // filtering unused operations - .where((e) => e is! OperationDefinitionNode || e == operation) - .toList(), - ).accept(visitor); + final visitor = GeneratorVisitor(context: context); + final documentDefinitions = DocumentNode(definitions: definitions); + documentDefinitions.accept(visitor); return QueryDefinition( name: name, operationName: operationName, - document: document, + document: documentDefinitions, classes: [ ...canonicalVisitor.enums .where((e) => context.usedEnums.contains(e.name)), diff --git a/pubspec.yaml b/pubspec.yaml index ee2239f8..59b6a44e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: artemis -version: 7.0.0-beta.10 +version: 7.0.0-beta.11 description: Build dart types from GraphQL schemas and queries (using Introspection Query). homepage: https://github.com/comigor/artemis @@ -22,7 +22,7 @@ dependencies: gql_http_link: ^0.4.0-nullsafety.1 gql_link: ^0.4.0-nullsafety.3 gql: ^0.13.0-nullsafety.2 - http: ^0.13.2 + http: ^0.13.3 json_annotation: ^4.0.1 meta: ^1.3.0 path: ^1.8.0 @@ -32,7 +32,7 @@ dependencies: dev_dependencies: args: ^2.1.0 - build_runner: ^2.0.1 + build_runner: ^2.0.2 build_test: ^2.1.0 json_serializable: ^4.1.1 build_resolvers: ^2.0.1 diff --git a/test/query_generator/fragments/fragment_multiple_queries_test.dart b/test/query_generator/fragments/fragment_multiple_queries_test.dart new file mode 100644 index 00000000..6cffc871 --- /dev/null +++ b/test/query_generator/fragments/fragment_multiple_queries_test.dart @@ -0,0 +1,419 @@ +import 'package:artemis/generator/data/data.dart'; +import 'package:test/test.dart'; + +import '../../helpers.dart'; + +void main() { + group('On fragments', () { + test( + 'One fragment with multiple queries per file', + () async => testGenerator( + query: r''' + query getPokemon($name: String!) { + pokemon(name: $name) { + ...pokemonFragment + } + } + + query getAllPokemons($first: Int!) { + pokemons(first: $first) { + ...pokemonFragment + } + } + ''', + schema: r''' + schema { + query: Query + } + + type Attack { + name: String + type: String + damage: Int + } + + type Pokemon { + id: ID! + number: String + name: String + weight: PokemonDimension + height: PokemonDimension + classification: String + types: [String] + resistant: [String] + attacks: PokemonAttack + weaknesses: [String] + fleeRate: Float + maxCP: Int + evolutions: [Pokemon] + evolutionRequirements: PokemonEvolutionRequirement + maxHP: Int + image: String + } + + type PokemonAttack { + fast: [Attack] + special: [Attack] + } + + type PokemonDimension { + minimum: String + maximum: String + } + + type PokemonEvolutionRequirement { + amount: Int + name: String + } + + type Query { + pokemons(first: Int!): [Pokemon] + pokemon(id: String, name: String): Pokemon + } + + ''', + builderOptionsMap: {'fragments_glob': '**.frag'}, + sourceAssetsMap: {'a|fragment.frag': fragmentsString}, + libraryDefinition: libraryDefinition, + generatedFile: generatedFile, + generateHelpers: true, + ), + ); + }); +} + +const fragmentsString = ''' + fragment pokemonFragment on Pokemon { + number + name + } +'''; + +final LibraryDefinition libraryDefinition = + LibraryDefinition(basename: r'query.graphql', queries: [ + QueryDefinition( + name: QueryName(name: r'GetPokemon$_Query'), + operationName: r'getPokemon', + classes: [ + ClassDefinition( + name: ClassName(name: r'GetPokemon$_Query$_Pokemon'), + mixins: [FragmentName(name: r'PokemonFragmentMixin')], + factoryPossibilities: {}, + typeNameField: ClassPropertyName(name: r'__typename'), + isInput: false), + ClassDefinition( + name: ClassName(name: r'GetPokemon$_Query'), + properties: [ + ClassProperty( + type: TypeName(name: r'GetPokemon$_Query$_Pokemon'), + name: ClassPropertyName(name: r'pokemon'), + isResolveType: false) + ], + factoryPossibilities: {}, + typeNameField: ClassPropertyName(name: r'__typename'), + isInput: false), + FragmentClassDefinition( + name: FragmentName(name: r'PokemonFragmentMixin'), + properties: [ + ClassProperty( + type: TypeName(name: r'String'), + name: ClassPropertyName(name: r'number'), + isResolveType: false), + ClassProperty( + type: TypeName(name: r'String'), + name: ClassPropertyName(name: r'name'), + isResolveType: false) + ]) + ], + inputs: [ + QueryInput( + type: TypeName(name: r'String', isNonNull: true), + name: QueryInputName(name: r'name')) + ], + generateHelpers: true, + suffix: r'Query'), + QueryDefinition( + name: QueryName(name: r'GetAllPokemons$_Query'), + operationName: r'getAllPokemons', + classes: [ + ClassDefinition( + name: ClassName(name: r'GetAllPokemons$_Query$_Pokemon'), + mixins: [FragmentName(name: r'PokemonFragmentMixin')], + factoryPossibilities: {}, + typeNameField: ClassPropertyName(name: r'__typename'), + isInput: false), + ClassDefinition( + name: ClassName(name: r'GetAllPokemons$_Query'), + properties: [ + ClassProperty( + type: ListOfTypeName( + typeName: + TypeName(name: r'GetAllPokemons$_Query$_Pokemon'), + isNonNull: false), + name: ClassPropertyName(name: r'pokemons'), + isResolveType: false) + ], + factoryPossibilities: {}, + typeNameField: ClassPropertyName(name: r'__typename'), + isInput: false), + FragmentClassDefinition( + name: FragmentName(name: r'PokemonFragmentMixin'), + properties: [ + ClassProperty( + type: TypeName(name: r'String'), + name: ClassPropertyName(name: r'number'), + isResolveType: false), + ClassProperty( + type: TypeName(name: r'String'), + name: ClassPropertyName(name: r'name'), + isResolveType: false) + ]) + ], + inputs: [ + QueryInput( + type: TypeName(name: r'int', isNonNull: true), + name: QueryInputName(name: r'first')) + ], + generateHelpers: true, + suffix: r'Query') +]); + +const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND +// @dart = 2.12 + +import 'package:artemis/artemis.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:equatable/equatable.dart'; +import 'package:gql/ast.dart'; +part 'query.graphql.g.dart'; + +mixin PokemonFragmentMixin { + String? number; + String? name; +} + +@JsonSerializable(explicitToJson: true) +class GetPokemon$Query$Pokemon extends JsonSerializable + with EquatableMixin, PokemonFragmentMixin { + GetPokemon$Query$Pokemon(); + + factory GetPokemon$Query$Pokemon.fromJson(Map json) => + _$GetPokemon$Query$PokemonFromJson(json); + + @override + List get props => [number, name]; + Map toJson() => _$GetPokemon$Query$PokemonToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetPokemon$Query extends JsonSerializable with EquatableMixin { + GetPokemon$Query(); + + factory GetPokemon$Query.fromJson(Map json) => + _$GetPokemon$QueryFromJson(json); + + GetPokemon$Query$Pokemon? pokemon; + + @override + List get props => [pokemon]; + Map toJson() => _$GetPokemon$QueryToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetAllPokemons$Query$Pokemon extends JsonSerializable + with EquatableMixin, PokemonFragmentMixin { + GetAllPokemons$Query$Pokemon(); + + factory GetAllPokemons$Query$Pokemon.fromJson(Map json) => + _$GetAllPokemons$Query$PokemonFromJson(json); + + @override + List get props => [number, name]; + Map toJson() => _$GetAllPokemons$Query$PokemonToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetAllPokemons$Query extends JsonSerializable with EquatableMixin { + GetAllPokemons$Query(); + + factory GetAllPokemons$Query.fromJson(Map json) => + _$GetAllPokemons$QueryFromJson(json); + + List? pokemons; + + @override + List get props => [pokemons]; + Map toJson() => _$GetAllPokemons$QueryToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetPokemonArguments extends JsonSerializable with EquatableMixin { + GetPokemonArguments({required this.name}); + + @override + factory GetPokemonArguments.fromJson(Map json) => + _$GetPokemonArgumentsFromJson(json); + + late String name; + + @override + List get props => [name]; + @override + Map toJson() => _$GetPokemonArgumentsToJson(this); +} + +final GET_POKEMON_QUERY_DOCUMENT = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.query, + name: NameNode(value: 'getPokemon'), + variableDefinitions: [ + VariableDefinitionNode( + variable: VariableNode(name: NameNode(value: 'name')), + type: + NamedTypeNode(name: NameNode(value: 'String'), isNonNull: true), + defaultValue: DefaultValueNode(value: null), + directives: []) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'pokemon'), + alias: null, + arguments: [ + ArgumentNode( + name: NameNode(value: 'name'), + value: VariableNode(name: NameNode(value: 'name'))) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'pokemonFragment'), directives: []) + ])) + ])), + FragmentDefinitionNode( + name: NameNode(value: 'pokemonFragment'), + typeCondition: TypeConditionNode( + on: NamedTypeNode( + name: NameNode(value: 'Pokemon'), isNonNull: false)), + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'number'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])) +]); + +class GetPokemonQuery + extends GraphQLQuery { + GetPokemonQuery({required this.variables}); + + @override + final DocumentNode document = GET_POKEMON_QUERY_DOCUMENT; + + @override + final String operationName = 'getPokemon'; + + @override + final GetPokemonArguments variables; + + @override + List get props => [document, operationName, variables]; + @override + GetPokemon$Query parse(Map json) => + GetPokemon$Query.fromJson(json); +} + +@JsonSerializable(explicitToJson: true) +class GetAllPokemonsArguments extends JsonSerializable with EquatableMixin { + GetAllPokemonsArguments({required this.first}); + + @override + factory GetAllPokemonsArguments.fromJson(Map json) => + _$GetAllPokemonsArgumentsFromJson(json); + + late int first; + + @override + List get props => [first]; + @override + Map toJson() => _$GetAllPokemonsArgumentsToJson(this); +} + +final GET_ALL_POKEMONS_QUERY_DOCUMENT = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.query, + name: NameNode(value: 'getAllPokemons'), + variableDefinitions: [ + VariableDefinitionNode( + variable: VariableNode(name: NameNode(value: 'first')), + type: NamedTypeNode(name: NameNode(value: 'Int'), isNonNull: true), + defaultValue: DefaultValueNode(value: null), + directives: []) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'pokemons'), + alias: null, + arguments: [ + ArgumentNode( + name: NameNode(value: 'first'), + value: VariableNode(name: NameNode(value: 'first'))) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'pokemonFragment'), directives: []) + ])) + ])), + FragmentDefinitionNode( + name: NameNode(value: 'pokemonFragment'), + typeCondition: TypeConditionNode( + on: NamedTypeNode( + name: NameNode(value: 'Pokemon'), isNonNull: false)), + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'number'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])) +]); + +class GetAllPokemonsQuery + extends GraphQLQuery { + GetAllPokemonsQuery({required this.variables}); + + @override + final DocumentNode document = GET_ALL_POKEMONS_QUERY_DOCUMENT; + + @override + final String operationName = 'getAllPokemons'; + + @override + final GetAllPokemonsArguments variables; + + @override + List get props => [document, operationName, variables]; + @override + GetAllPokemons$Query parse(Map json) => + GetAllPokemons$Query.fromJson(json); +} +'''; diff --git a/test/query_generator/multiple_operations_per_file_test.dart b/test/query_generator/multiple_operations_per_file_test.dart index 6f7f1cfa..16b4e20f 100644 --- a/test/query_generator/multiple_operations_per_file_test.dart +++ b/test/query_generator/multiple_operations_per_file_test.dart @@ -289,66 +289,6 @@ final MUT_DATA_MUTATION_DOCUMENT = DocumentNode(definitions: [ directives: [], selectionSet: null) ])) - ])), - OperationDefinitionNode( - type: OperationType.query, - name: NameNode(value: 'QueData'), - variableDefinitions: [ - VariableDefinitionNode( - variable: VariableNode(name: NameNode(value: 'intsNonNullable')), - type: ListTypeNode( - type: NamedTypeNode( - name: NameNode(value: 'Int'), isNonNull: false), - isNonNull: true), - defaultValue: DefaultValueNode(value: null), - directives: []), - VariableDefinitionNode( - variable: VariableNode(name: NameNode(value: 'stringNullable')), - type: NamedTypeNode( - name: NameNode(value: 'String'), isNonNull: false), - defaultValue: DefaultValueNode(value: null), - directives: []) - ], - directives: [], - selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 'que'), - alias: null, - arguments: [ - ArgumentNode( - name: NameNode(value: 'intsNonNullable'), - value: - VariableNode(name: NameNode(value: 'intsNonNullable'))), - ArgumentNode( - name: NameNode(value: 'stringNullable'), - value: VariableNode(name: NameNode(value: 'stringNullable'))) - ], - directives: [], - selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 's'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'i'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'list'), - alias: null, - arguments: [ - ArgumentNode( - name: NameNode(value: 'intsNonNullable'), - value: VariableNode( - name: NameNode(value: 'intsNonNullable'))) - ], - directives: [], - selectionSet: null) - ])) ])) ]); @@ -390,37 +330,6 @@ class QueDataArguments extends JsonSerializable with EquatableMixin { } final QUE_DATA_QUERY_DOCUMENT = DocumentNode(definitions: [ - OperationDefinitionNode( - type: OperationType.mutation, - name: NameNode(value: 'MutData'), - variableDefinitions: [ - VariableDefinitionNode( - variable: VariableNode(name: NameNode(value: 'input')), - type: - NamedTypeNode(name: NameNode(value: 'Input'), isNonNull: true), - defaultValue: DefaultValueNode(value: null), - directives: []) - ], - directives: [], - selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 'mut'), - alias: null, - arguments: [ - ArgumentNode( - name: NameNode(value: 'input'), - value: VariableNode(name: NameNode(value: 'input'))) - ], - directives: [], - selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 's'), - alias: null, - arguments: [], - directives: [], - selectionSet: null) - ])) - ])), OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'QueData'),