From 8066992c04b56db1bd768b1a0cfb5bfb6e7d8491 Mon Sep 17 00:00:00 2001 From: Christian Budde Christensen Date: Sun, 7 Nov 2021 10:31:57 +0000 Subject: [PATCH] fix: Broken fragment spreads --- .../lib/src/utils/expand_fragments.dart | 38 ++-- ..._type_of.dart => possible_types_test.dart} | 0 ...ine_and_named_fragments_are_same_test.dart | 191 ++++++++++++++++++ .../query_fragments/named_fragments_test.dart | 20 +- .../union_type_inline_fragments_test.dart | 36 +++- ...ne_fragments_with_possible_types_test.dart | 74 +++++++ 6 files changed, 324 insertions(+), 35 deletions(-) rename packages/normalize/test/{possible_type_of.dart => possible_types_test.dart} (100%) create mode 100644 packages/normalize/test/query_fragments/inline_and_named_fragments_are_same_test.dart create mode 100644 packages/normalize/test/query_fragments/union_type_inline_fragments_with_possible_types_test.dart diff --git a/packages/normalize/lib/src/utils/expand_fragments.dart b/packages/normalize/lib/src/utils/expand_fragments.dart index 81816853..71aa72ee 100644 --- a/packages/normalize/lib/src/utils/expand_fragments.dart +++ b/packages/normalize/lib/src/utils/expand_fragments.dart @@ -18,41 +18,35 @@ List expandFragments({ if (typename == null) { continue; } + String fragmentOnName; + SelectionSetNode fragmentSelectionSet; if (selectionNode is InlineFragmentNode) { - final fragmentOnName = - selectionNode.typeCondition?.on.name.value ?? typename; - - // We'll add the fields if - // - We know the current type (typename != null) - // and - // - If the fragment and current type are the same (fragmentOnName == typename) - // - Or the current type is a type of the fragment target - if (fragmentOnName == typename || - possibleTypes[fragmentOnName]?.contains(typename) == true) { - fieldNodes.addAll( - expandFragments( - typename: typename, - selectionSet: selectionNode.selectionSet, - fragmentMap: fragmentMap, - possibleTypes: possibleTypes, - ), - ); - } + fragmentOnName = selectionNode.typeCondition?.on.name.value ?? typename; + fragmentSelectionSet = selectionNode.selectionSet; } else if (selectionNode is FragmentSpreadNode) { final fragment = fragmentMap[selectionNode.name.value]; if (fragment == null) { throw Exception('Missing fragment ${selectionNode.name.value}'); } + fragmentOnName = fragment.typeCondition.on.name.value; + fragmentSelectionSet = fragment.selectionSet; + } else { + throw (FormatException('Unknown selection node type')); + } + // We'll add the fields if + // - We know the current type (typename != null) AND + // - If the fragment and current type are the same (fragmentOnName == typename) OR + // - the current type is a type of the fragment target + if (fragmentOnName == typename || + possibleTypes[fragmentOnName]?.contains(typename) == true) { fieldNodes.addAll( expandFragments( typename: typename, - selectionSet: fragment.selectionSet, + selectionSet: fragmentSelectionSet, fragmentMap: fragmentMap, possibleTypes: possibleTypes, ), ); - } else { - throw (FormatException('Unknown selection node type')); } } return List.from(_mergeSelections(fieldNodes)); diff --git a/packages/normalize/test/possible_type_of.dart b/packages/normalize/test/possible_types_test.dart similarity index 100% rename from packages/normalize/test/possible_type_of.dart rename to packages/normalize/test/possible_types_test.dart diff --git a/packages/normalize/test/query_fragments/inline_and_named_fragments_are_same_test.dart b/packages/normalize/test/query_fragments/inline_and_named_fragments_are_same_test.dart new file mode 100644 index 00000000..5f649db5 --- /dev/null +++ b/packages/normalize/test/query_fragments/inline_and_named_fragments_are_same_test.dart @@ -0,0 +1,191 @@ +import 'package:test/test.dart'; +import 'package:gql/language.dart'; + +import 'package:normalize/normalize.dart'; + +void main() { + group('Union Type Inline Fragments With Possible Types', () { + final inlineFragmentQuery = parseString(''' + query TestQuery { + booksAndAuthors { + id + __typename + ... on Book { + title + } + ... on Author { + name + } + } + } + '''); + final namedFragmentQuery = parseString(''' + query TestQuery { + booksAndAuthors { + id + __typename + ...BookFragment + ...AuthorFragment + } + } + fragment BookFragment on Book { + title + } + fragment AuthorFragment on Author { + name + } + '''); + + final data = { + 'booksAndAuthors': [ + {'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'}, + {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + ] + }; + final dataDeserializedWithoutPossibleTypes = { + 'booksAndAuthors': [ + { + 'id': '123', + '__typename': 'Book', + 'title': 'My awesome blog post', + }, + {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + ] + }; + + final normalizedMapWithPossibleTypes = { + 'Query': { + 'booksAndAuthors': [ + {'\$ref': 'Book:123'}, + {'\$ref': 'Author:324'} + ] + }, + 'Book:123': { + 'id': '123', + '__typename': 'Book', + 'title': 'My awesome blog post' + }, + 'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + }; + final normalizedMapWithoutPossibleTypes = { + 'Query': { + 'booksAndAuthors': [ + {'\$ref': 'Book:123'}, + {'\$ref': 'Author:324'} + ] + }, + 'Book:123': { + 'id': '123', + '__typename': 'Book', + 'title': 'My awesome blog post', + }, + 'Author:324': { + 'id': '324', + '__typename': 'Author', + 'name': 'Nicole', + } + }; + final possibleTypes = { + 'BookAndAuthor': {'Book', 'Author'} + }; + test('Produces same normalized object with possible types', () { + final inlineNormalizedResult = {}; + normalizeOperation( + read: (dataId) => inlineNormalizedResult[dataId], + write: (dataId, value) => inlineNormalizedResult[dataId] = value, + document: inlineFragmentQuery, + data: data, + possibleTypes: possibleTypes, + ); + final namedFragmentNormalizedResult = {}; + normalizeOperation( + read: (dataId) => namedFragmentNormalizedResult[dataId], + write: (dataId, value) => namedFragmentNormalizedResult[dataId] = value, + document: inlineFragmentQuery, + data: data, + possibleTypes: possibleTypes, + ); + + expect( + inlineNormalizedResult, + equals(namedFragmentNormalizedResult), + ); + expect( + inlineNormalizedResult, + equals(normalizedMapWithPossibleTypes), + ); + }); + test('Produces same normalized object without possible types', () { + final inlineNormalizedResult = {}; + normalizeOperation( + read: (dataId) => inlineNormalizedResult[dataId], + write: (dataId, value) => inlineNormalizedResult[dataId] = value, + document: inlineFragmentQuery, + data: data, + ); + final namedFragmentNormalizedResult = {}; + normalizeOperation( + read: (dataId) => namedFragmentNormalizedResult[dataId], + write: (dataId, value) => namedFragmentNormalizedResult[dataId] = value, + document: inlineFragmentQuery, + data: data, + ); + + expect( + inlineNormalizedResult, + equals(namedFragmentNormalizedResult), + ); + expect( + inlineNormalizedResult, + equals(normalizedMapWithoutPossibleTypes), + ); + }); + + test('Produces correct nested data object with possible types', () { + expect( + denormalizeOperation( + document: inlineFragmentQuery, + read: (dataId) => normalizedMapWithPossibleTypes[dataId], + possibleTypes: possibleTypes, + ), + equals( + denormalizeOperation( + document: namedFragmentQuery, + read: (dataId) => normalizedMapWithPossibleTypes[dataId], + possibleTypes: possibleTypes, + ), + ), + ); + expect( + denormalizeOperation( + document: inlineFragmentQuery, + read: (dataId) => normalizedMapWithPossibleTypes[dataId], + possibleTypes: possibleTypes, + ), + equals(data), + ); + }); + + test('Produces correct nested data object without possible types', () { + expect( + denormalizeOperation( + document: inlineFragmentQuery, + read: (dataId) => normalizedMapWithoutPossibleTypes[dataId], + ), + equals( + denormalizeOperation( + document: namedFragmentQuery, + read: (dataId) => normalizedMapWithoutPossibleTypes[dataId], + ), + ), + ); + expect( + denormalizeOperation( + document: inlineFragmentQuery, + read: (dataId) => normalizedMapWithoutPossibleTypes[dataId], + ), + equals(dataDeserializedWithoutPossibleTypes), + ); + }); + }); +} diff --git a/packages/normalize/test/query_fragments/named_fragments_test.dart b/packages/normalize/test/query_fragments/named_fragments_test.dart index 44c55cd1..4cc659cf 100644 --- a/packages/normalize/test/query_fragments/named_fragments_test.dart +++ b/packages/normalize/test/query_fragments/named_fragments_test.dart @@ -42,11 +42,13 @@ void main() { test('Produces correct normalized object', () { final normalizedResult = {}; normalizeOperation( - read: (dataId) => normalizedResult[dataId], - write: (dataId, value) => normalizedResult[dataId] = value, - document: query, - data: sharedResponse, - ); + read: (dataId) => normalizedResult[dataId], + write: (dataId, value) => normalizedResult[dataId] = value, + document: query, + data: sharedResponse, + possibleTypes: { + 'Person': {'Author'} + }); expect( normalizedResult, @@ -57,9 +59,11 @@ void main() { test('Produces correct nested data object', () { expect( denormalizeOperation( - document: query, - read: (dataId) => sharedNormalizedMap[dataId], - ), + document: query, + read: (dataId) => sharedNormalizedMap[dataId], + possibleTypes: { + 'Person': {'Author'} + }), equals(sharedResponse), ); }); diff --git a/packages/normalize/test/query_fragments/union_type_inline_fragments_test.dart b/packages/normalize/test/query_fragments/union_type_inline_fragments_test.dart index 862f1376..c9d66b36 100644 --- a/packages/normalize/test/query_fragments/union_type_inline_fragments_test.dart +++ b/packages/normalize/test/query_fragments/union_type_inline_fragments_test.dart @@ -22,8 +22,30 @@ void main() { final data = { 'booksAndAuthors': [ - {'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'}, - {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + { + 'id': '123', + '__typename': 'Book', + 'title': 'My awesome blog post', + }, + { + 'id': '324', + '__typename': 'Author', + 'name': 'Nicole', + } + ] + }; + final denormalizedData = { + 'booksAndAuthors': [ + { + 'id': '123', + '__typename': 'Book', + 'title': 'My awesome blog post', + }, + { + 'id': '324', + '__typename': 'Author', + 'name': 'Nicole', + } ] }; @@ -37,9 +59,13 @@ void main() { 'Book:123': { 'id': '123', '__typename': 'Book', - 'title': 'My awesome blog post' + 'title': 'My awesome blog post', }, - 'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + 'Author:324': { + 'id': '324', + '__typename': 'Author', + 'name': 'Nicole', + } }; test('Produces correct normalized object', () { @@ -63,7 +89,7 @@ void main() { document: query, read: (dataId) => normalizedMap[dataId], ), - equals(data), + equals(denormalizedData), ); }); }); diff --git a/packages/normalize/test/query_fragments/union_type_inline_fragments_with_possible_types_test.dart b/packages/normalize/test/query_fragments/union_type_inline_fragments_with_possible_types_test.dart new file mode 100644 index 00000000..c3000e2d --- /dev/null +++ b/packages/normalize/test/query_fragments/union_type_inline_fragments_with_possible_types_test.dart @@ -0,0 +1,74 @@ +import 'package:test/test.dart'; +import 'package:gql/language.dart'; + +import 'package:normalize/normalize.dart'; + +void main() { + group('Union Type Inline Fragments With Possible Types', () { + final query = parseString(''' + query TestQuery { + booksAndAuthors { + id + __typename + ... on Book { + title + } + ... on Author { + name + } + } + } + '''); + + final data = { + 'booksAndAuthors': [ + {'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'}, + {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + ] + }; + + final normalizedMap = { + 'Query': { + 'booksAndAuthors': [ + {'\$ref': 'Book:123'}, + {'\$ref': 'Author:324'} + ] + }, + 'Book:123': { + 'id': '123', + '__typename': 'Book', + 'title': 'My awesome blog post' + }, + 'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'} + }; + final possibleTypes = { + 'BookAndAuthor': {'Book', 'Author'} + }; + test('Produces correct normalized object', () { + final normalizedResult = {}; + normalizeOperation( + read: (dataId) => normalizedResult[dataId], + write: (dataId, value) => normalizedResult[dataId] = value, + document: query, + data: data, + possibleTypes: possibleTypes, + ); + + expect( + normalizedResult, + equals(normalizedMap), + ); + }); + + test('Produces correct nested data object', () { + expect( + denormalizeOperation( + document: query, + read: (dataId) => normalizedMap[dataId], + possibleTypes: possibleTypes, + ), + equals(data), + ); + }); + }); +}