Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8066992

Browse files
committedNov 20, 2021
fix: Broken fragment spreads
1 parent ecb082d commit 8066992

6 files changed

+324
-35
lines changed
 

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

+16-22
Original file line numberDiff line numberDiff line change
@@ -18,41 +18,35 @@ 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+
// - We know the current type (typename != null) AND
38+
// - If the fragment and current type are the same (fragmentOnName == typename) OR
39+
// - the current type is a type of the fragment target
40+
if (fragmentOnName == typename ||
41+
possibleTypes[fragmentOnName]?.contains(typename) == true) {
4642
fieldNodes.addAll(
4743
expandFragments(
4844
typename: typename,
49-
selectionSet: fragment.selectionSet,
45+
selectionSet: fragmentSelectionSet,
5046
fragmentMap: fragmentMap,
5147
possibleTypes: possibleTypes,
5248
),
5349
);
54-
} else {
55-
throw (FormatException('Unknown selection node type'));
5650
}
5751
}
5852
return List.from(_mergeSelections(fieldNodes));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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 data = {
40+
'booksAndAuthors': [
41+
{'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'},
42+
{'id': '324', '__typename': 'Author', 'name': 'Nicole'}
43+
]
44+
};
45+
final dataDeserializedWithoutPossibleTypes = {
46+
'booksAndAuthors': [
47+
{
48+
'id': '123',
49+
'__typename': 'Book',
50+
'title': 'My awesome blog post',
51+
},
52+
{'id': '324', '__typename': 'Author', 'name': 'Nicole'}
53+
]
54+
};
55+
56+
final normalizedMapWithPossibleTypes = {
57+
'Query': {
58+
'booksAndAuthors': [
59+
{'\$ref': 'Book:123'},
60+
{'\$ref': 'Author:324'}
61+
]
62+
},
63+
'Book:123': {
64+
'id': '123',
65+
'__typename': 'Book',
66+
'title': 'My awesome blog post'
67+
},
68+
'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'}
69+
};
70+
final normalizedMapWithoutPossibleTypes = {
71+
'Query': {
72+
'booksAndAuthors': [
73+
{'\$ref': 'Book:123'},
74+
{'\$ref': 'Author:324'}
75+
]
76+
},
77+
'Book:123': {
78+
'id': '123',
79+
'__typename': 'Book',
80+
'title': 'My awesome blog post',
81+
},
82+
'Author:324': {
83+
'id': '324',
84+
'__typename': 'Author',
85+
'name': 'Nicole',
86+
}
87+
};
88+
final possibleTypes = {
89+
'BookAndAuthor': {'Book', 'Author'}
90+
};
91+
test('Produces same normalized object with possible types', () {
92+
final inlineNormalizedResult = {};
93+
normalizeOperation(
94+
read: (dataId) => inlineNormalizedResult[dataId],
95+
write: (dataId, value) => inlineNormalizedResult[dataId] = value,
96+
document: inlineFragmentQuery,
97+
data: data,
98+
possibleTypes: possibleTypes,
99+
);
100+
final namedFragmentNormalizedResult = {};
101+
normalizeOperation(
102+
read: (dataId) => namedFragmentNormalizedResult[dataId],
103+
write: (dataId, value) => namedFragmentNormalizedResult[dataId] = value,
104+
document: inlineFragmentQuery,
105+
data: data,
106+
possibleTypes: possibleTypes,
107+
);
108+
109+
expect(
110+
inlineNormalizedResult,
111+
equals(namedFragmentNormalizedResult),
112+
);
113+
expect(
114+
inlineNormalizedResult,
115+
equals(normalizedMapWithPossibleTypes),
116+
);
117+
});
118+
test('Produces same normalized object without possible types', () {
119+
final inlineNormalizedResult = {};
120+
normalizeOperation(
121+
read: (dataId) => inlineNormalizedResult[dataId],
122+
write: (dataId, value) => inlineNormalizedResult[dataId] = value,
123+
document: inlineFragmentQuery,
124+
data: data,
125+
);
126+
final namedFragmentNormalizedResult = {};
127+
normalizeOperation(
128+
read: (dataId) => namedFragmentNormalizedResult[dataId],
129+
write: (dataId, value) => namedFragmentNormalizedResult[dataId] = value,
130+
document: inlineFragmentQuery,
131+
data: data,
132+
);
133+
134+
expect(
135+
inlineNormalizedResult,
136+
equals(namedFragmentNormalizedResult),
137+
);
138+
expect(
139+
inlineNormalizedResult,
140+
equals(normalizedMapWithoutPossibleTypes),
141+
);
142+
});
143+
144+
test('Produces correct nested data object with possible types', () {
145+
expect(
146+
denormalizeOperation(
147+
document: inlineFragmentQuery,
148+
read: (dataId) => normalizedMapWithPossibleTypes[dataId],
149+
possibleTypes: possibleTypes,
150+
),
151+
equals(
152+
denormalizeOperation(
153+
document: namedFragmentQuery,
154+
read: (dataId) => normalizedMapWithPossibleTypes[dataId],
155+
possibleTypes: possibleTypes,
156+
),
157+
),
158+
);
159+
expect(
160+
denormalizeOperation(
161+
document: inlineFragmentQuery,
162+
read: (dataId) => normalizedMapWithPossibleTypes[dataId],
163+
possibleTypes: possibleTypes,
164+
),
165+
equals(data),
166+
);
167+
});
168+
169+
test('Produces correct nested data object without possible types', () {
170+
expect(
171+
denormalizeOperation(
172+
document: inlineFragmentQuery,
173+
read: (dataId) => normalizedMapWithoutPossibleTypes[dataId],
174+
),
175+
equals(
176+
denormalizeOperation(
177+
document: namedFragmentQuery,
178+
read: (dataId) => normalizedMapWithoutPossibleTypes[dataId],
179+
),
180+
),
181+
);
182+
expect(
183+
denormalizeOperation(
184+
document: inlineFragmentQuery,
185+
read: (dataId) => normalizedMapWithoutPossibleTypes[dataId],
186+
),
187+
equals(dataDeserializedWithoutPossibleTypes),
188+
);
189+
});
190+
});
191+
}

‎packages/normalize/test/query_fragments/named_fragments_test.dart

+12-8
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ void main() {
4242
test('Produces correct normalized object', () {
4343
final normalizedResult = {};
4444
normalizeOperation(
45-
read: (dataId) => normalizedResult[dataId],
46-
write: (dataId, value) => normalizedResult[dataId] = value,
47-
document: query,
48-
data: sharedResponse,
49-
);
45+
read: (dataId) => normalizedResult[dataId],
46+
write: (dataId, value) => normalizedResult[dataId] = value,
47+
document: query,
48+
data: sharedResponse,
49+
possibleTypes: {
50+
'Person': {'Author'}
51+
});
5052

5153
expect(
5254
normalizedResult,
@@ -57,9 +59,11 @@ void main() {
5759
test('Produces correct nested data object', () {
5860
expect(
5961
denormalizeOperation(
60-
document: query,
61-
read: (dataId) => sharedNormalizedMap[dataId],
62-
),
62+
document: query,
63+
read: (dataId) => sharedNormalizedMap[dataId],
64+
possibleTypes: {
65+
'Person': {'Author'}
66+
}),
6367
equals(sharedResponse),
6468
);
6569
});

‎packages/normalize/test/query_fragments/union_type_inline_fragments_test.dart

+31-5
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,30 @@ 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+
},
44+
{
45+
'id': '324',
46+
'__typename': 'Author',
47+
'name': 'Nicole',
48+
}
2749
]
2850
};
2951

@@ -37,9 +59,13 @@ void main() {
3759
'Book:123': {
3860
'id': '123',
3961
'__typename': 'Book',
40-
'title': 'My awesome blog post'
62+
'title': 'My awesome blog post',
4163
},
42-
'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'}
64+
'Author:324': {
65+
'id': '324',
66+
'__typename': 'Author',
67+
'name': 'Nicole',
68+
}
4369
};
4470

4571
test('Produces correct normalized object', () {
@@ -63,7 +89,7 @@ void main() {
6389
document: query,
6490
read: (dataId) => normalizedMap[dataId],
6591
),
66-
equals(data),
92+
equals(denormalizedData),
6793
);
6894
});
6995
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 query = 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+
23+
final data = {
24+
'booksAndAuthors': [
25+
{'id': '123', '__typename': 'Book', 'title': 'My awesome blog post'},
26+
{'id': '324', '__typename': 'Author', 'name': 'Nicole'}
27+
]
28+
};
29+
30+
final normalizedMap = {
31+
'Query': {
32+
'booksAndAuthors': [
33+
{'\$ref': 'Book:123'},
34+
{'\$ref': 'Author:324'}
35+
]
36+
},
37+
'Book:123': {
38+
'id': '123',
39+
'__typename': 'Book',
40+
'title': 'My awesome blog post'
41+
},
42+
'Author:324': {'id': '324', '__typename': 'Author', 'name': 'Nicole'}
43+
};
44+
final possibleTypes = {
45+
'BookAndAuthor': {'Book', 'Author'}
46+
};
47+
test('Produces correct normalized object', () {
48+
final normalizedResult = {};
49+
normalizeOperation(
50+
read: (dataId) => normalizedResult[dataId],
51+
write: (dataId, value) => normalizedResult[dataId] = value,
52+
document: query,
53+
data: data,
54+
possibleTypes: possibleTypes,
55+
);
56+
57+
expect(
58+
normalizedResult,
59+
equals(normalizedMap),
60+
);
61+
});
62+
63+
test('Produces correct nested data object', () {
64+
expect(
65+
denormalizeOperation(
66+
document: query,
67+
read: (dataId) => normalizedMap[dataId],
68+
possibleTypes: possibleTypes,
69+
),
70+
equals(data),
71+
);
72+
});
73+
});
74+
}

0 commit comments

Comments
 (0)
Please sign in to comment.