Skip to content

Commit 82bea52

Browse files
committed
fix: Broken fragment spreads
1 parent ecb082d commit 82bea52

10 files changed

+328
-33
lines changed

packages/normalize/lib/src/config/normalization_config.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class NormalizationConfig {
2727
final bool allowPartialData;
2828

2929
/// A map from an interface/union to possible types.
30-
final Map<String, Set<String>> possibleTypes;
30+
final Map<String, Set<String>>? possibleTypes;
3131

3232
NormalizationConfig({
3333
required this.read,

packages/normalize/lib/src/denormalize_fragment.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Map<String, dynamic>? denormalizeFragment({
3434
bool returnPartialData = false,
3535
bool handleException = true,
3636
String referenceKey = '\$ref',
37-
Map<String, Set<String>> possibleTypes = const {},
37+
Map<String, Set<String>>? possibleTypes,
3838
}) {
3939
if (addTypename) {
4040
document = transform(

packages/normalize/lib/src/denormalize_operation.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Map<String, dynamic>? denormalizeOperation({
2929
bool returnPartialData = false,
3030
bool handleException = true,
3131
String referenceKey = '\$ref',
32-
Map<String, Set<String>> possibleTypes = const {},
32+
Map<String, Set<String>>? possibleTypes,
3333
}) {
3434
if (addTypename) {
3535
document = transform(

packages/normalize/lib/src/normalize_fragment.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void normalizeFragment({
3939
bool addTypename = false,
4040
String referenceKey = '\$ref',
4141
bool acceptPartialData = true,
42-
Map<String, Set<String>> possibleTypes = const {},
42+
Map<String, Set<String>>? possibleTypes,
4343
}) {
4444
// Always add typenames to ensure data is stored with typename
4545
document = transform(

packages/normalize/lib/src/normalize_operation.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ void normalizeOperation({
3535
bool addTypename = false,
3636
bool acceptPartialData = true,
3737
String referenceKey = '\$ref',
38-
Map<String, Set<String>> possibleTypes = const {},
38+
Map<String, Set<String>>? possibleTypes,
3939
}) {
4040
if (addTypename) {
4141
document = transform(

packages/normalize/lib/src/utils/expand_fragments.dart

+20-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ List<FieldNode> expandFragments({
66
required String? typename,
77
required SelectionSetNode selectionSet,
88
required Map<String, FragmentDefinitionNode> fragmentMap,
9-
required Map<String, Set<String>> possibleTypes,
9+
required Map<String, Set<String>>? possibleTypes,
1010
}) {
1111
final fieldNodes = <FieldNode>[];
1212

@@ -18,41 +18,38 @@ List<FieldNode> expandFragments({
1818
if (typename == null) {
1919
continue;
2020
}
21+
String fragmentOnName;
22+
SelectionSetNode fragmentSelectionSet;
2123
if (selectionNode is InlineFragmentNode) {
22-
final fragmentOnName =
23-
selectionNode.typeCondition?.on.name.value ?? typename;
24-
25-
// We'll add the fields if
26-
// - We know the current type (typename != null)
27-
// and
28-
// - If the fragment and current type are the same (fragmentOnName == typename)
29-
// - Or the current type is a type of the fragment target
30-
if (fragmentOnName == typename ||
31-
possibleTypes[fragmentOnName]?.contains(typename) == true) {
32-
fieldNodes.addAll(
33-
expandFragments(
34-
typename: typename,
35-
selectionSet: selectionNode.selectionSet,
36-
fragmentMap: fragmentMap,
37-
possibleTypes: possibleTypes,
38-
),
39-
);
40-
}
24+
fragmentOnName = selectionNode.typeCondition?.on.name.value ?? typename;
25+
fragmentSelectionSet = selectionNode.selectionSet;
4126
} else if (selectionNode is FragmentSpreadNode) {
4227
final fragment = fragmentMap[selectionNode.name.value];
4328
if (fragment == null) {
4429
throw Exception('Missing fragment ${selectionNode.name.value}');
4530
}
31+
fragmentOnName = fragment.typeCondition.on.name.value;
32+
fragmentSelectionSet = fragment.selectionSet;
33+
} else {
34+
throw (FormatException('Unknown selection node type'));
35+
}
36+
// We'll add the fields if
37+
// - The possible types map is `null`
38+
// OR
39+
// - We know the current type (typename != null) AND
40+
// - If the fragment and current type are the same (fragmentOnName == typename) OR
41+
// - the current type is a type of the fragment target
42+
if (possibleTypes == null ||
43+
fragmentOnName == typename ||
44+
possibleTypes[fragmentOnName]?.contains(typename) == true) {
4645
fieldNodes.addAll(
4746
expandFragments(
4847
typename: typename,
49-
selectionSet: fragment.selectionSet,
48+
selectionSet: fragmentSelectionSet,
5049
fragmentMap: fragmentMap,
5150
possibleTypes: possibleTypes,
5251
),
5352
);
54-
} else {
55-
throw (FormatException('Unknown selection node type'));
5653
}
5754
}
5855
return List.from(_mergeSelections(fieldNodes));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import 'package:test/test.dart';
2+
import 'package:gql/language.dart';
3+
4+
import 'package:normalize/normalize.dart';
5+
6+
void main() {
7+
group('Union Type Inline Fragments With Possible Types', () {
8+
final inlineFragmentQuery = parseString('''
9+
query TestQuery {
10+
booksAndAuthors {
11+
id
12+
__typename
13+
... on Book {
14+
title
15+
}
16+
... on Author {
17+
name
18+
}
19+
}
20+
}
21+
''');
22+
final namedFragmentQuery = parseString('''
23+
query TestQuery {
24+
booksAndAuthors {
25+
id
26+
__typename
27+
...BookFragment
28+
...AuthorFragment
29+
}
30+
}
31+
fragment BookFragment on Book {
32+
title
33+
}
34+
fragment AuthorFragment on Author {
35+
name
36+
}
37+
''');
38+
39+
final dataWithPossibleTypes = {
40+
'booksAndAuthors': [
41+
{'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'},
42+
{'id': '324', '__typename': 'Author', 'name': 'Nicole'}
43+
]
44+
};
45+
final dataWithoutPossibleTypes = {
46+
'booksAndAuthors': [
47+
{
48+
'id': '123',
49+
'__typename': 'Book',
50+
'title': 'My awesome blog post',
51+
'name': null
52+
},
53+
{'id': '324', '__typename': 'Author', 'name': 'Nicole', 'title': null}
54+
]
55+
};
56+
57+
final normalizedMapWithPossibleTypes = {
58+
'Query': {
59+
'booksAndAuthors': [
60+
{'\$ref': 'Book:123'},
61+
{'\$ref': 'Author:324'}
62+
]
63+
},
64+
'Book:123': {
65+
'id': '123',
66+
'__typename': 'Book',
67+
'title': 'My awesome blog post'
68+
},
69+
'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'}
70+
};
71+
final normalizedMapWithoutPossibleTypes = {
72+
'Query': {
73+
'booksAndAuthors': [
74+
{'\$ref': 'Book:123'},
75+
{'\$ref': 'Author:324'}
76+
]
77+
},
78+
'Book:123': {
79+
'id': '123',
80+
'__typename': 'Book',
81+
'title': 'My awesome blog post',
82+
'name': null
83+
},
84+
'Author:324': {
85+
'id': '324',
86+
'__typename': 'Author',
87+
'name': 'Nicole',
88+
'title': null
89+
}
90+
};
91+
final possibleTypes = {
92+
'BookAndAuthor': {'Book', 'Author'}
93+
};
94+
test('Produces same normalized object with possible types', () {
95+
final inlineNormalizedResult = {};
96+
normalizeOperation(
97+
read: (dataId) => inlineNormalizedResult[dataId],
98+
write: (dataId, value) => inlineNormalizedResult[dataId] = value,
99+
document: inlineFragmentQuery,
100+
data: dataWithPossibleTypes,
101+
possibleTypes: possibleTypes,
102+
);
103+
final namedFragmentNormalizedResult = {};
104+
normalizeOperation(
105+
read: (dataId) => namedFragmentNormalizedResult[dataId],
106+
write: (dataId, value) => namedFragmentNormalizedResult[dataId] = value,
107+
document: inlineFragmentQuery,
108+
data: dataWithPossibleTypes,
109+
possibleTypes: possibleTypes,
110+
);
111+
112+
expect(
113+
inlineNormalizedResult,
114+
equals(namedFragmentNormalizedResult),
115+
);
116+
expect(
117+
inlineNormalizedResult,
118+
equals(normalizedMapWithPossibleTypes),
119+
);
120+
});
121+
test('Produces same normalized object without possible types', () {
122+
final inlineNormalizedResult = {};
123+
normalizeOperation(
124+
read: (dataId) => inlineNormalizedResult[dataId],
125+
write: (dataId, value) => inlineNormalizedResult[dataId] = value,
126+
document: inlineFragmentQuery,
127+
data: dataWithoutPossibleTypes,
128+
);
129+
final namedFragmentNormalizedResult = {};
130+
normalizeOperation(
131+
read: (dataId) => namedFragmentNormalizedResult[dataId],
132+
write: (dataId, value) => namedFragmentNormalizedResult[dataId] = value,
133+
document: inlineFragmentQuery,
134+
data: dataWithoutPossibleTypes,
135+
);
136+
137+
expect(
138+
inlineNormalizedResult,
139+
equals(namedFragmentNormalizedResult),
140+
);
141+
expect(
142+
inlineNormalizedResult,
143+
equals(normalizedMapWithoutPossibleTypes),
144+
);
145+
});
146+
147+
test('Produces correct nested data object with possible types', () {
148+
expect(
149+
denormalizeOperation(
150+
document: inlineFragmentQuery,
151+
read: (dataId) => normalizedMapWithPossibleTypes[dataId],
152+
possibleTypes: possibleTypes,
153+
),
154+
equals(
155+
denormalizeOperation(
156+
document: namedFragmentQuery,
157+
read: (dataId) => normalizedMapWithPossibleTypes[dataId],
158+
possibleTypes: possibleTypes,
159+
),
160+
),
161+
);
162+
expect(
163+
denormalizeOperation(
164+
document: inlineFragmentQuery,
165+
read: (dataId) => normalizedMapWithPossibleTypes[dataId],
166+
possibleTypes: possibleTypes,
167+
),
168+
equals(dataWithPossibleTypes),
169+
);
170+
});
171+
172+
test('Produces correct nested data object without possible types', () {
173+
expect(
174+
denormalizeOperation(
175+
document: inlineFragmentQuery,
176+
read: (dataId) => normalizedMapWithoutPossibleTypes[dataId],
177+
),
178+
equals(
179+
denormalizeOperation(
180+
document: namedFragmentQuery,
181+
read: (dataId) => normalizedMapWithoutPossibleTypes[dataId],
182+
),
183+
),
184+
);
185+
expect(
186+
denormalizeOperation(
187+
document: inlineFragmentQuery,
188+
read: (dataId) => normalizedMapWithoutPossibleTypes[dataId],
189+
),
190+
equals(dataWithoutPossibleTypes),
191+
);
192+
});
193+
});
194+
}

packages/normalize/test/query_fragments/union_type_inline_fragments_test.dart

+35-5
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,32 @@ void main() {
2222

2323
final data = {
2424
'booksAndAuthors': [
25-
{'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'},
26-
{'id': '324', '__typename': 'Author', 'name': 'Nicole'}
25+
{
26+
'id': '123',
27+
'__typename': 'Book',
28+
'title': 'My awesome blog post',
29+
},
30+
{
31+
'id': '324',
32+
'__typename': 'Author',
33+
'name': 'Nicole',
34+
}
35+
]
36+
};
37+
final denormalizedData = {
38+
'booksAndAuthors': [
39+
{
40+
'id': '123',
41+
'__typename': 'Book',
42+
'title': 'My awesome blog post',
43+
'name': null,
44+
},
45+
{
46+
'id': '324',
47+
'__typename': 'Author',
48+
'name': 'Nicole',
49+
'title': null,
50+
}
2751
]
2852
};
2953

@@ -37,9 +61,15 @@ void main() {
3761
'Book:123': {
3862
'id': '123',
3963
'__typename': 'Book',
40-
'title': 'My awesome blog post'
64+
'title': 'My awesome blog post',
65+
'name': null,
4166
},
42-
'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'}
67+
'Author:324': {
68+
'id': '324',
69+
'__typename': 'Author',
70+
'name': 'Nicole',
71+
'title': null,
72+
}
4373
};
4474

4575
test('Produces correct normalized object', () {
@@ -63,7 +93,7 @@ void main() {
6393
document: query,
6494
read: (dataId) => normalizedMap[dataId],
6595
),
66-
equals(data),
96+
equals(denormalizedData),
6797
);
6898
});
6999
});

0 commit comments

Comments
 (0)