Skip to content

Commit 963dadf

Browse files
committed
use schema to compare possibleTypes for abstract types
1 parent 0230158 commit 963dadf

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

src/methods/validate-fixture-input.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function validateFixtureInput(
6767
errors.push(`Cannot validate ${responseKey}: missing parent type information`);
6868
} else {
6969
const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1];
70-
if (isValueExpectedForType(currentValue, parentType, typenameResponseKey)) {
70+
if (isValueExpectedForType(currentValue, parentType, schema, typenameResponseKey)) {
7171
errors.push(`Missing expected fixture data for ${responseKey}`);
7272
}
7373
}
@@ -253,28 +253,23 @@ function processNestedArrays(
253253
* Determines if a fixture value is expected for a given parent type based on its __typename.
254254
*
255255
* @param fixtureValue - The fixture value to check
256-
* @param parentType - The parent type from typeInfo (concrete type if inside inline fragment, abstract if on union/interface)
256+
* @param parentType - The parent type from typeInfo
257+
* @param schema - The GraphQL schema to resolve possible types for abstract types
257258
* @param typenameKey - The response key for the __typename field (supports aliases like `type: __typename`)
258259
* @returns True if the value is expected for the parent type, false otherwise
259260
*
260261
* @remarks
261-
* When the parent type is abstract (union/interface), all values are expected.
262-
* When the parent type is concrete (inside an inline fragment), only values
262+
* When the parent type is abstract (union/interface), checks if the value's __typename
263+
* is one of the possible types for that abstract type.
264+
* When the parent type is concrete (e.g., inside `... on ConcreteType`), only values
263265
* whose __typename matches the concrete type are expected.
264266
*/
265267
function isValueExpectedForType(
266268
fixtureValue: any,
267269
parentType: GraphQLCompositeType,
270+
schema: GraphQLSchema,
268271
typenameKey?: string
269272
): boolean {
270-
// If parent type is abstract (union/interface), all values are expected.
271-
// This means we're validating a field selected directly on the abstract type (e.g., __typename on a union),
272-
// so it should be present on all values regardless of their concrete type.
273-
if (isAbstractType(parentType)) {
274-
return true;
275-
}
276-
277-
// Parent is a concrete type - check if fixture value's __typename matches
278273
// If __typename wasn't selected in the query, we can't discriminate, so expect all values
279274
if (!typenameKey) {
280275
return true;
@@ -286,6 +281,12 @@ function isValueExpectedForType(
286281
return true;
287282
}
288283

289-
// Only expect the value for this type if its __typename matches the parent type
284+
// If parent type is abstract (union/interface), check if the value's type is one of the possible types
285+
if (isAbstractType(parentType)) {
286+
const possibleTypes = schema.getPossibleTypes(parentType);
287+
return possibleTypes.some(type => type.name === valueTypename);
288+
}
289+
290+
// Parent is a concrete type - check if fixture value's __typename matches
290291
return valueTypename === parentType.name;
291292
}

test/fixtures/test-schema.graphql

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ type DataContainer {
1515
searchResults: [SearchResult!]!
1616
itemMatrix: [[Item]]
1717
metadataCube: [[[Metadata]]]
18+
products: [Product!]!
1819
}
1920

2021
union SearchResult = Item | Metadata
2122

23+
union Product = PhysicalProduct | DigitalProduct | GiftCard
24+
25+
interface Purchasable {
26+
price: Int!
27+
currency: String!
28+
}
29+
2230
type Item {
2331
id: ID
2432
count: Int!
@@ -78,4 +86,23 @@ input HttpRequest {
7886
method: String!
7987
headers: String
8088
body: String
89+
}
90+
91+
type PhysicalProduct implements Purchasable {
92+
price: Int!
93+
currency: String!
94+
weight: Int!
95+
sku: String!
96+
}
97+
98+
type DigitalProduct implements Purchasable {
99+
price: Int!
100+
currency: String!
101+
downloadUrl: String!
102+
fileSize: Int!
103+
}
104+
105+
type GiftCard {
106+
code: String!
107+
balance: Int!
81108
}

test/methods/validate-fixture-input.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,52 @@ describe("validateFixtureInput", () => {
211211
expect(result.errors).toHaveLength(0);
212212
});
213213

214+
it("handles inline fragment on interface type", () => {
215+
const queryAST = parse(`
216+
query {
217+
data {
218+
products {
219+
__typename
220+
... on Purchasable {
221+
price
222+
currency
223+
}
224+
... on GiftCard {
225+
code
226+
balance
227+
}
228+
}
229+
}
230+
}
231+
`);
232+
233+
const fixtureInput = {
234+
data: {
235+
products: [
236+
{
237+
__typename: "PhysicalProduct",
238+
price: 1000,
239+
currency: "USD"
240+
},
241+
{
242+
__typename: "DigitalProduct",
243+
price: 500,
244+
currency: "USD"
245+
},
246+
{
247+
__typename: "GiftCard",
248+
code: "GIFT123",
249+
balance: 5000
250+
}
251+
]
252+
}
253+
};
254+
255+
const result = validateFixtureInput(queryAST, schema, fixtureInput);
256+
257+
expect(result.errors).toHaveLength(0);
258+
});
259+
214260
it("handles single inline fragment on union without __typename", () => {
215261
const queryAST = parse(`
216262
query {

0 commit comments

Comments
 (0)