Skip to content

Commit d82c439

Browse files
committed
Support executing operations with fragment variables
1 parent 6b20b31 commit d82c439

File tree

6 files changed

+484
-70
lines changed

6 files changed

+484
-70
lines changed

src/execution/__tests__/variables-test.ts

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ const TestComplexScalar = new GraphQLScalarType({
6060
},
6161
});
6262

63+
const NestedType: GraphQLObjectType = new GraphQLObjectType({
64+
name: 'NestedType',
65+
fields: {
66+
echo: fieldWithInputArg({ type: GraphQLString }),
67+
},
68+
});
69+
6370
const TestInputObject = new GraphQLInputObjectType({
6471
name: 'TestInputObject',
6572
fields: {
@@ -129,6 +136,10 @@ const TestType = new GraphQLObjectType({
129136
type: TestNestedInputObject,
130137
defaultValue: 'Hello World',
131138
}),
139+
nested: {
140+
type: NestedType,
141+
resolve: () => ({}),
142+
},
132143
list: fieldWithInputArg({ type: new GraphQLList(GraphQLString) }),
133144
nnList: fieldWithInputArg({
134145
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
@@ -154,6 +165,14 @@ function executeQuery(
154165
return executeSync({ schema, document, variableValues });
155166
}
156167

168+
function executeQueryWithFragmentArguments(
169+
query: string,
170+
variableValues?: { [variable: string]: unknown },
171+
) {
172+
const document = parse(query, { experimentalFragmentArguments: true });
173+
return executeSync({ schema, document, variableValues });
174+
}
175+
157176
describe('Execute: Handles inputs', () => {
158177
describe('Handles objects and nullability', () => {
159178
describe('using inline structs', () => {
@@ -1136,4 +1155,283 @@ describe('Execute: Handles inputs', () => {
11361155
});
11371156
});
11381157
});
1158+
1159+
describe('using fragment arguments', () => {
1160+
it('when there are no fragment arguments', () => {
1161+
const result = executeQueryWithFragmentArguments(`
1162+
query {
1163+
...a
1164+
}
1165+
fragment a on TestType {
1166+
fieldWithNonNullableStringInput(input: "A")
1167+
}
1168+
`);
1169+
expect(result).to.deep.equal({
1170+
data: {
1171+
fieldWithNonNullableStringInput: '"A"',
1172+
},
1173+
});
1174+
});
1175+
1176+
it('when a value is required and provided', () => {
1177+
const result = executeQueryWithFragmentArguments(`
1178+
query {
1179+
...a(value: "A")
1180+
}
1181+
fragment a($value: String!) on TestType {
1182+
fieldWithNonNullableStringInput(input: $value)
1183+
}
1184+
`);
1185+
expect(result).to.deep.equal({
1186+
data: {
1187+
fieldWithNonNullableStringInput: '"A"',
1188+
},
1189+
});
1190+
});
1191+
1192+
it('when a value is required and not provided', () => {
1193+
const result = executeQueryWithFragmentArguments(`
1194+
query {
1195+
...a
1196+
}
1197+
fragment a($value: String!) on TestType {
1198+
fieldWithNullableStringInput(input: $value)
1199+
}
1200+
`);
1201+
1202+
expect(result).to.have.property('errors');
1203+
expect(result.errors).to.have.length(1);
1204+
expect(result.errors?.[0]?.message).to.match(
1205+
/Argument "value" of required type "String!"/,
1206+
);
1207+
});
1208+
1209+
it('when the definition has a default and is provided', () => {
1210+
const result = executeQueryWithFragmentArguments(`
1211+
query {
1212+
...a(value: "A")
1213+
}
1214+
fragment a($value: String! = "B") on TestType {
1215+
fieldWithNonNullableStringInput(input: $value)
1216+
}
1217+
`);
1218+
expect(result).to.deep.equal({
1219+
data: {
1220+
fieldWithNonNullableStringInput: '"A"',
1221+
},
1222+
});
1223+
});
1224+
1225+
it('when the definition has a default and is not provided', () => {
1226+
const result = executeQueryWithFragmentArguments(`
1227+
query {
1228+
...a
1229+
}
1230+
fragment a($value: String! = "B") on TestType {
1231+
fieldWithNonNullableStringInput(input: $value)
1232+
}
1233+
`);
1234+
expect(result).to.deep.equal({
1235+
data: {
1236+
fieldWithNonNullableStringInput: '"B"',
1237+
},
1238+
});
1239+
});
1240+
1241+
it('when a definition has a default, is not provided, and spreads another fragment', () => {
1242+
const result = executeQueryWithFragmentArguments(`
1243+
query {
1244+
...a
1245+
}
1246+
fragment a($a: String! = "B") on TestType {
1247+
...b(b: $a)
1248+
}
1249+
fragment b($b: String!) on TestType {
1250+
fieldWithNonNullableStringInput(input: $b)
1251+
}
1252+
`);
1253+
expect(result).to.deep.equal({
1254+
data: {
1255+
fieldWithNonNullableStringInput: '"B"',
1256+
},
1257+
});
1258+
});
1259+
1260+
it('when the definition has a non-nullable default and is provided null', () => {
1261+
const result = executeQueryWithFragmentArguments(`
1262+
query {
1263+
...a(value: null)
1264+
}
1265+
fragment a($value: String! = "B") on TestType {
1266+
fieldWithNullableStringInput(input: $value)
1267+
}
1268+
`);
1269+
1270+
expect(result).to.have.property('errors');
1271+
expect(result.errors).to.have.length(1);
1272+
expect(result.errors?.[0]?.message).to.match(
1273+
/Argument "value" of non-null type "String!"/,
1274+
);
1275+
});
1276+
1277+
it('when the definition has no default and is not provided', () => {
1278+
const result = executeQueryWithFragmentArguments(`
1279+
query {
1280+
...a
1281+
}
1282+
fragment a($value: String) on TestType {
1283+
fieldWithNonNullableStringInputAndDefaultArgumentValue(input: $value)
1284+
}
1285+
`);
1286+
expect(result).to.deep.equal({
1287+
data: {
1288+
fieldWithNonNullableStringInputAndDefaultArgumentValue:
1289+
'"Hello World"',
1290+
},
1291+
});
1292+
});
1293+
1294+
it('when an argument is shadowed by an operation variable', () => {
1295+
const result = executeQueryWithFragmentArguments(`
1296+
query($x: String! = "A") {
1297+
...a(x: "B")
1298+
}
1299+
fragment a($x: String) on TestType {
1300+
fieldWithNullableStringInput(input: $x)
1301+
}
1302+
`);
1303+
expect(result).to.deep.equal({
1304+
data: {
1305+
fieldWithNullableStringInput: '"B"',
1306+
},
1307+
});
1308+
});
1309+
1310+
it('when a nullable argument with a field default is not provided and shadowed by an operation variable', () => {
1311+
const result = executeQueryWithFragmentArguments(`
1312+
query($x: String = "A") {
1313+
...a
1314+
}
1315+
fragment a($x: String) on TestType {
1316+
fieldWithNonNullableStringInputAndDefaultArgumentValue(input: $x)
1317+
}
1318+
`);
1319+
expect(result).to.deep.equal({
1320+
data: {
1321+
fieldWithNonNullableStringInputAndDefaultArgumentValue:
1322+
'"Hello World"',
1323+
},
1324+
});
1325+
});
1326+
1327+
it('when a fragment-variable is shadowed by an intermediate fragment-spread but defined in the operation-variables', () => {
1328+
const result = executeQueryWithFragmentArguments(`
1329+
query($x: String = "A") {
1330+
...a
1331+
}
1332+
fragment a($x: String) on TestType {
1333+
...b
1334+
}
1335+
fragment b on TestType {
1336+
fieldWithNullableStringInput(input: $x)
1337+
}
1338+
`);
1339+
expect(result).to.deep.equal({
1340+
data: {
1341+
fieldWithNullableStringInput: '"A"',
1342+
},
1343+
});
1344+
});
1345+
1346+
it('when a fragment is used with different args', () => {
1347+
const result = executeQueryWithFragmentArguments(`
1348+
query($x: String = "Hello") {
1349+
a: nested {
1350+
...a(x: "a")
1351+
}
1352+
b: nested {
1353+
...a(x: "b", b: true)
1354+
}
1355+
hello: nested {
1356+
...a(x: $x)
1357+
}
1358+
}
1359+
fragment a($x: String, $b: Boolean = false) on NestedType {
1360+
a: echo(input: $x) @skip(if: $b)
1361+
b: echo(input: $x) @include(if: $b)
1362+
}
1363+
`);
1364+
expect(result).to.deep.equal({
1365+
data: {
1366+
a: {
1367+
a: '"a"',
1368+
},
1369+
b: {
1370+
b: '"b"',
1371+
},
1372+
hello: {
1373+
a: '"Hello"',
1374+
},
1375+
},
1376+
});
1377+
});
1378+
1379+
it('when the argument variable is nested in a complex type', () => {
1380+
const result = executeQueryWithFragmentArguments(`
1381+
query {
1382+
...a(value: "C")
1383+
}
1384+
fragment a($value: String) on TestType {
1385+
list(input: ["A", "B", $value, "D"])
1386+
}
1387+
`);
1388+
expect(result).to.deep.equal({
1389+
data: {
1390+
list: '["A", "B", "C", "D"]',
1391+
},
1392+
});
1393+
});
1394+
1395+
it('when argument variables are used recursively', () => {
1396+
const result = executeQueryWithFragmentArguments(`
1397+
query {
1398+
...a(aValue: "C")
1399+
}
1400+
fragment a($aValue: String) on TestType {
1401+
...b(bValue: $aValue)
1402+
}
1403+
fragment b($bValue: String) on TestType {
1404+
list(input: ["A", "B", $bValue, "D"])
1405+
}
1406+
`);
1407+
expect(result).to.deep.equal({
1408+
data: {
1409+
list: '["A", "B", "C", "D"]',
1410+
},
1411+
});
1412+
});
1413+
1414+
it('when argument passed in as list', () => {
1415+
const result = executeQueryWithFragmentArguments(`
1416+
query Q($opValue: String = "op") {
1417+
...a(aValue: "A")
1418+
}
1419+
fragment a($aValue: String, $bValue: String) on TestType {
1420+
...b(aValue: [$aValue, "B"], bValue: [$bValue, $opValue])
1421+
}
1422+
fragment b($aValue: [String], $bValue: [String], $cValue: String) on TestType {
1423+
aList: list(input: $aValue)
1424+
bList: list(input: $bValue)
1425+
cList: list(input: [$cValue])
1426+
}
1427+
`);
1428+
expect(result).to.deep.equal({
1429+
data: {
1430+
aList: '["A", "B"]',
1431+
bList: '[null, "op"]',
1432+
cList: '[null]',
1433+
},
1434+
});
1435+
});
1436+
});
11391437
});

0 commit comments

Comments
 (0)