Skip to content

Commit 02e8411

Browse files
committed
early break for missing typename on abstract type
1 parent de722e8 commit 02e8411

File tree

3 files changed

+46
-18
lines changed

3 files changed

+46
-18
lines changed

src/methods/validate-fixture-input.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export function validateFixtureInput(
223223
errors.push(
224224
`Missing __typename field for abstract type ${getNamedType(typeInfo.getType())?.name}`
225225
);
226+
return BREAK;
226227
}
227228
}
228229
},

test/fixtures/test-schema.graphql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ type InterfaceImplementer3 implements HasId {
5757
id: ID!
5858
}
5959

60-
type InterfaceImplementer4 {
60+
type NoInterfacesImplemented {
6161
value: String
6262
}
6363

64-
union InterfaceImplementersUnion = InterfaceImplementer1 | InterfaceImplementer2 | InterfaceImplementer3 | InterfaceImplementer4
64+
union InterfaceImplementersUnion = InterfaceImplementer1 | InterfaceImplementer2 | InterfaceImplementer3 | NoInterfacesImplemented
6565

6666
union NestedOuter = NestedOuterA | NestedOuterB
6767

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

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,9 @@ describe("validateFixtureInput", () => {
253253
const result = validateFixtureInput(queryAST, schema, fixtureInput);
254254

255255
// - inner SelectionSet has typenameResponseKey = undefined (doesn't inherit "outerType")
256-
// - Error 1: Missing __typename (inner has 2+ fragments without it)
257-
// - Error 2: Missing value field (without typename, can't discriminate, expects all fields)
258-
expect(result.errors).toHaveLength(2);
256+
// - Detects missing __typename and BREAKs early
257+
expect(result.errors).toHaveLength(1);
259258
expect(result.errors[0]).toBe("Missing __typename field for abstract type NestedInner");
260-
expect(result.errors[1]).toBe("Missing expected fixture data for value");
261259
});
262260

263261
it("handles nested unions with typename at each level", () => {
@@ -487,7 +485,7 @@ describe("validateFixtureInput", () => {
487485
description: "Also has all three"
488486
},
489487
{}
490-
// Empty object - InterfaceImplementer4 that doesn't implement any interface
488+
// Empty object - NoInterfacesImplemented that doesn't implement any interface
491489
// Valid because InterfaceImplementersUnion {1,2,3,4} was narrowed to HasId {1,2,3}
492490
]
493491
}
@@ -583,7 +581,7 @@ describe("validateFixtureInput", () => {
583581
// Implements HasId only
584582
},
585583
{
586-
__typename: "InterfaceImplementer4"
584+
__typename: "NoInterfacesImplemented"
587585
// Doesn't implement any interface
588586
}
589587
]
@@ -635,7 +633,7 @@ describe("validateFixtureInput", () => {
635633
// This is a valid response - nested fragments don't match
636634
},
637635
{}
638-
// InterfaceImplementer4: doesn't implement any interface
636+
// NoInterfacesImplemented: doesn't implement any interface
639637
// Empty object is valid - handled by empty object logic
640638
]
641639
}
@@ -1512,6 +1510,42 @@ describe("validateFixtureInput", () => {
15121510
expect(result.errors[0]).toBe("Missing expected fixture data for price");
15131511
});
15141512

1513+
it("handles multiple inline fragments on same type without typename", () => {
1514+
const queryAST = parse(`
1515+
query {
1516+
data {
1517+
searchResults {
1518+
... on Item {
1519+
id
1520+
}
1521+
... on Item {
1522+
count
1523+
}
1524+
}
1525+
}
1526+
}
1527+
`);
1528+
1529+
const fixtureInput = {
1530+
data: {
1531+
searchResults: [
1532+
{
1533+
id: "gid://test/Item/1",
1534+
count: 5
1535+
}
1536+
]
1537+
}
1538+
};
1539+
1540+
const result = validateFixtureInput(queryAST, schema, fixtureInput);
1541+
1542+
// Multiple fragments but all on the same type (Item)
1543+
// Still errors on missing __typename because fragmentSpreadCount > 1
1544+
// However, NO cascading field errors because all fragments select on same type
1545+
expect(result.errors).toHaveLength(1);
1546+
expect(result.errors[0]).toBe("Missing __typename field for abstract type SearchResult");
1547+
});
1548+
15151549
it("detects missing fields when __typename is not selected in union with inline fragments", () => {
15161550
const queryAST = parse(`
15171551
query {
@@ -1548,16 +1582,9 @@ describe("validateFixtureInput", () => {
15481582
const result = validateFixtureInput(queryAST, schema, fixtureInput);
15491583

15501584
// Without __typename, we can't discriminate which fields are expected for each object
1551-
// So the validator conservatively expects all fields from all inline fragments
1552-
// First error: Missing __typename field for abstract type (required for discrimination)
1553-
// First object is missing email and phone (from Metadata fragment)
1554-
// Second object is missing id and count (from Item fragment)
1555-
expect(result.errors).toHaveLength(5);
1585+
// Validator detects missing __typename for abstract type with 2+ fragments and BREAKs early
1586+
expect(result.errors).toHaveLength(1);
15561587
expect(result.errors[0]).toBe("Missing __typename field for abstract type SearchResult");
1557-
expect(result.errors[1]).toBe("Missing expected fixture data for id");
1558-
expect(result.errors[2]).toBe("Missing expected fixture data for count");
1559-
expect(result.errors[3]).toBe("Missing expected fixture data for email");
1560-
expect(result.errors[4]).toBe("Missing expected fixture data for phone");
15611588
});
15621589
});
15631590
});

0 commit comments

Comments
 (0)