Skip to content

Commit 90d048d

Browse files
committed
Tests for fragment args
1 parent 522f495 commit 90d048d

11 files changed

+725
-15
lines changed

src/execution/__tests__/variables-test.ts

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ function fieldWithInputArg(
8282
};
8383
}
8484

85+
const NestedType: GraphQLObjectType = new GraphQLObjectType({
86+
name: 'NestedType',
87+
fields: {
88+
echo: fieldWithInputArg({ type: GraphQLString }),
89+
},
90+
});
91+
8592
const TestType = new GraphQLObjectType({
8693
name: 'TestType',
8794
fields: {
@@ -107,6 +114,10 @@ const TestType = new GraphQLObjectType({
107114
defaultValue: 'Hello World',
108115
}),
109116
list: fieldWithInputArg({ type: new GraphQLList(GraphQLString) }),
117+
nested: {
118+
type: NestedType,
119+
resolve: () => ({}),
120+
},
110121
nnList: fieldWithInputArg({
111122
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
112123
}),
@@ -1006,6 +1017,222 @@ describe('Execute: Handles inputs', () => {
10061017
});
10071018
});
10081019

1020+
describe('using fragment arguments', () => {
1021+
it('when there are no fragment arguments', () => {
1022+
const result = executeQuery(`
1023+
query {
1024+
...a
1025+
}
1026+
fragment a on TestType {
1027+
fieldWithNonNullableStringInput(input: "A")
1028+
}
1029+
`);
1030+
expect(result).to.deep.equal({
1031+
data: {
1032+
fieldWithNonNullableStringInput: '"A"',
1033+
},
1034+
});
1035+
});
1036+
1037+
it('when a value is required and provided', () => {
1038+
const result = executeQuery(`
1039+
query {
1040+
...a(value: "A")
1041+
}
1042+
fragment a($value: String!) on TestType {
1043+
fieldWithNonNullableStringInput(input: $value)
1044+
}
1045+
`);
1046+
expect(result).to.deep.equal({
1047+
data: {
1048+
fieldWithNonNullableStringInput: '"A"',
1049+
},
1050+
});
1051+
});
1052+
1053+
it('when a value is required and not provided', () => {
1054+
const result = executeQuery(`
1055+
query {
1056+
...a
1057+
}
1058+
fragment a($value: String!) on TestType {
1059+
fieldWithNullableStringInput(input: $value)
1060+
}
1061+
`);
1062+
expect(result).to.deep.equal({
1063+
data: {
1064+
fieldWithNullableStringInput: null,
1065+
},
1066+
});
1067+
});
1068+
1069+
it('when the definition has a default and is provided', () => {
1070+
const result = executeQuery(`
1071+
query {
1072+
...a(value: "A")
1073+
}
1074+
fragment a($value: String! = "B") on TestType {
1075+
fieldWithNonNullableStringInput(input: $value)
1076+
}
1077+
`);
1078+
expect(result).to.deep.equal({
1079+
data: {
1080+
fieldWithNonNullableStringInput: '"A"',
1081+
},
1082+
});
1083+
});
1084+
1085+
it('when the definition has a default and is not provided', () => {
1086+
const result = executeQuery(`
1087+
query {
1088+
...a
1089+
}
1090+
fragment a($value: String! = "B") on TestType {
1091+
fieldWithNonNullableStringInput(input: $value)
1092+
}
1093+
`);
1094+
expect(result).to.deep.equal({
1095+
data: {
1096+
fieldWithNonNullableStringInput: '"B"',
1097+
},
1098+
});
1099+
});
1100+
1101+
it('when the definition has a non-nullable default and is provided null', () => {
1102+
const result = executeQuery(`
1103+
query {
1104+
...a(value: null)
1105+
}
1106+
fragment a($value: String! = "B") on TestType {
1107+
fieldWithNullableStringInput(input: $value)
1108+
}
1109+
`);
1110+
expect(result).to.deep.equal({
1111+
data: {
1112+
fieldWithNullableStringInput: 'null',
1113+
},
1114+
});
1115+
});
1116+
1117+
it('when the definition has no default and is not provided', () => {
1118+
const result = executeQuery(`
1119+
query {
1120+
...a
1121+
}
1122+
fragment a($value: String) on TestType {
1123+
fieldWithNonNullableStringInputAndDefaultArgumentValue(input: $value)
1124+
}
1125+
`);
1126+
expect(result).to.deep.equal({
1127+
data: {
1128+
fieldWithNonNullableStringInputAndDefaultArgumentValue:
1129+
'"Hello World"',
1130+
},
1131+
});
1132+
});
1133+
1134+
it('when an argument is shadowed by an operation variable', () => {
1135+
const result = executeQuery(`
1136+
query($x: String! = "A") {
1137+
...a(x: "B")
1138+
}
1139+
fragment a($x: String) on TestType {
1140+
fieldWithNullableStringInput(input: $x)
1141+
}
1142+
`);
1143+
expect(result).to.deep.equal({
1144+
data: {
1145+
fieldWithNullableStringInput: '"B"',
1146+
},
1147+
});
1148+
});
1149+
1150+
it('when a nullable argument with a field default is not provided and shadowed by an operation variable', () => {
1151+
const result = executeQuery(`
1152+
query($x: String = "A") {
1153+
...a
1154+
}
1155+
fragment a($x: String) on TestType {
1156+
fieldWithNonNullableStringInputAndDefaultArgumentValue(input: $x)
1157+
}
1158+
`);
1159+
expect(result).to.deep.equal({
1160+
data: {
1161+
fieldWithNonNullableStringInputAndDefaultArgumentValue:
1162+
'"Hello World"',
1163+
},
1164+
});
1165+
});
1166+
1167+
it('when a fragment is used with different args', () => {
1168+
const result = executeQuery(`
1169+
query($x: String = "Hello") {
1170+
a: nested {
1171+
...a(x: "a")
1172+
}
1173+
b: nested {
1174+
...a(x: "b", b: true)
1175+
}
1176+
hello: nested {
1177+
...a(x: $x)
1178+
}
1179+
}
1180+
fragment a($x: String, $b: Boolean = false) on NestedType {
1181+
a: echo(input: $x) @skip(if: $b)
1182+
b: echo(input: $x) @include(if: $b)
1183+
}
1184+
`);
1185+
expect(result).to.deep.equal({
1186+
data: {
1187+
a: {
1188+
a: '"a"',
1189+
},
1190+
b: {
1191+
b: '"b"',
1192+
},
1193+
hello: {
1194+
a: '"Hello"',
1195+
},
1196+
},
1197+
});
1198+
});
1199+
1200+
it('when the argument variable is nested in a complex type', () => {
1201+
const result = executeQuery(`
1202+
query {
1203+
...a(value: "C")
1204+
}
1205+
fragment a($value: String) on TestType {
1206+
list(input: ["A", "B", $value, "D"])
1207+
}
1208+
`);
1209+
expect(result).to.deep.equal({
1210+
data: {
1211+
list: '["A", "B", "C", "D"]',
1212+
},
1213+
});
1214+
});
1215+
1216+
it('when argument variables are used recursively', () => {
1217+
const result = executeQuery(`
1218+
query {
1219+
...a(aValue: "C")
1220+
}
1221+
fragment a($aValue: String) on TestType {
1222+
...b(bValue: $aValue)
1223+
}
1224+
fragment b($bValue: String) on TestType {
1225+
list(input: ["A", "B", $bValue, "D"])
1226+
}
1227+
`);
1228+
expect(result).to.deep.equal({
1229+
data: {
1230+
list: '["A", "B", "C", "D"]',
1231+
},
1232+
});
1233+
});
1234+
});
1235+
10091236
describe('getVariableValues: limit maximum number of coercion errors', () => {
10101237
const doc = parse(`
10111238
query ($input: [String!]) {

src/language/__tests__/parser-test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -607,13 +607,16 @@ describe('Parser', () => {
607607
expect('loc' in result).to.equal(false);
608608
});
609609

610-
it('Legacy: allows parsing fragment defined variables', () => {
610+
it('allows parsing fragment defined arguments', () => {
611611
const document = 'fragment a($v: Boolean = false) on t { f(v: $v) }';
612612

613-
expect(() =>
614-
parse(document, { allowLegacyFragmentVariables: true }),
615-
).to.not.throw();
616-
expect(() => parse(document)).to.throw('Syntax Error');
613+
expect(() => parse(document)).to.not.throw();
614+
});
615+
616+
it('allows parsing fragment spread arguments', () => {
617+
const document = 'fragment a on t { ...b(v: $v) }';
618+
619+
expect(() => parse(document)).to.not.throw();
617620
});
618621

619622
it('contains location that can be Object.toStringified, JSON.stringified, or jsutils.inspected', () => {

src/language/__tests__/printer-test.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,34 +110,58 @@ describe('Printer: Query document', () => {
110110
`);
111111
});
112112

113-
it('Legacy: prints fragment with variable directives', () => {
114-
const queryASTWithVariableDirective = parse(
113+
it('prints fragment with argument definition directives', () => {
114+
const fragmentWithArgumentDefinitionDirective = parse(
115115
'fragment Foo($foo: TestType @test) on TestType @testDirective { id }',
116-
{ allowLegacyFragmentVariables: true },
117116
);
118-
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
117+
expect(print(fragmentWithArgumentDefinitionDirective)).to.equal(dedent`
119118
fragment Foo($foo: TestType @test) on TestType @testDirective {
120119
id
121120
}
122121
`);
123122
});
124123

125-
it('Legacy: correctly prints fragment defined variables', () => {
126-
const fragmentWithVariable = parse(
124+
it('correctly prints fragment defined arguments', () => {
125+
const fragmentWithArgumentDefinition = parse(
127126
`
128127
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
129128
id
130129
}
131130
`,
132-
{ allowLegacyFragmentVariables: true },
133131
);
134-
expect(print(fragmentWithVariable)).to.equal(dedent`
132+
expect(print(fragmentWithArgumentDefinition)).to.equal(dedent`
135133
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
136134
id
137135
}
138136
`);
139137
});
140138

139+
it('prints fragment spread with arguments', () => {
140+
const fragmentSpreadWithArguments = parse(
141+
'fragment Foo on TestType { ...Bar(a: {x: $x}, b: true) }',
142+
);
143+
expect(print(fragmentSpreadWithArguments)).to.equal(dedent`
144+
fragment Foo on TestType {
145+
...Bar(a: { x: $x }, b: true)
146+
}
147+
`);
148+
});
149+
150+
it('prints fragment spread with multi-line arguments', () => {
151+
const fragmentSpreadWithArguments = parse(
152+
'fragment Foo on TestType { ...Bar(a: {x: $x, y: $y, z: $z, xy: $xy}, b: true, c: "a long string extending arguments over max length") }',
153+
);
154+
expect(print(fragmentSpreadWithArguments)).to.equal(dedent`
155+
fragment Foo on TestType {
156+
...Bar(
157+
a: { x: $x, y: $y, z: $z, xy: $xy }
158+
b: true
159+
c: "a long string extending arguments over max length"
160+
)
161+
}
162+
`);
163+
});
164+
141165
it('prints kitchen sink without altering ast', () => {
142166
const ast = parse(kitchenSinkQuery, {
143167
noLocation: true,

src/language/__tests__/visitor-test.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,10 +455,9 @@ describe('Visitor', () => {
455455
]);
456456
});
457457

458-
it('Legacy: visits variables defined in fragments', () => {
458+
it('visits arguments defined on fragments', () => {
459459
const ast = parse('fragment a($v: Boolean = false) on t { f }', {
460460
noLocation: true,
461-
allowLegacyFragmentVariables: true,
462461
});
463462
const visited: Array<any> = [];
464463

@@ -505,6 +504,49 @@ describe('Visitor', () => {
505504
]);
506505
});
507506

507+
it('visits arguments on fragment spreads', () => {
508+
const ast = parse('fragment a on t { ...s(v: false) }', {
509+
noLocation: true,
510+
});
511+
const visited: Array<any> = [];
512+
513+
visit(ast, {
514+
enter(node) {
515+
checkVisitorFnArgs(ast, arguments);
516+
visited.push(['enter', node.kind, getValue(node)]);
517+
},
518+
leave(node) {
519+
checkVisitorFnArgs(ast, arguments);
520+
visited.push(['leave', node.kind, getValue(node)]);
521+
},
522+
});
523+
524+
expect(visited).to.deep.equal([
525+
['enter', 'Document', undefined],
526+
['enter', 'FragmentDefinition', undefined],
527+
['enter', 'Name', 'a'],
528+
['leave', 'Name', 'a'],
529+
['enter', 'NamedType', undefined],
530+
['enter', 'Name', 't'],
531+
['leave', 'Name', 't'],
532+
['leave', 'NamedType', undefined],
533+
['enter', 'SelectionSet', undefined],
534+
['enter', 'FragmentSpread', undefined],
535+
['enter', 'Name', 's'],
536+
['leave', 'Name', 's'],
537+
['enter', 'Argument', { kind: 'BooleanValue', value: false }],
538+
['enter', 'Name', 'v'],
539+
['leave', 'Name', 'v'],
540+
['enter', 'BooleanValue', false],
541+
['leave', 'BooleanValue', false],
542+
['leave', 'Argument', { kind: 'BooleanValue', value: false }],
543+
['leave', 'FragmentSpread', undefined],
544+
['leave', 'SelectionSet', undefined],
545+
['leave', 'FragmentDefinition', undefined],
546+
['leave', 'Document', undefined],
547+
]);
548+
});
549+
508550
it('n', () => {
509551
const ast = parse(kitchenSinkQuery, {
510552
experimentalClientControlledNullability: true,

0 commit comments

Comments
 (0)