From a3660208618fc0f20cbaba05eac05a6a07455c12 Mon Sep 17 00:00:00 2001 From: Lucas Traba Date: Mon, 16 Mar 2026 17:37:08 +0100 Subject: [PATCH] fix: parenthesize implementing type ternaries joined with || --- src/index.ts | 1 + .../__snapshots__/spec.ts.snap | 14 ++--- tests/useImplementingTypes/spec.ts | 10 ++-- .../__snapshots__/spec.ts.snap | 2 +- .../spec.ts | 2 +- .../__snapshots__/spec.ts.snap | 60 ++++++++++++++++++- .../spec.ts | 22 ++++++- .../threeImplSchema.ts | 31 ++++++++++ 8 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 tests/useImplementingTypesAndTerminateCircularRelationships/threeImplSchema.ts diff --git a/src/index.ts b/src/index.ts index 9efb6e5..7503e64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -404,6 +404,7 @@ const getNamedType = (opts: Options): }), ) .filter((value) => value !== null) + .map((v) => `(${v})`) .join(' || ') || null ); default: diff --git a/tests/useImplementingTypes/__snapshots__/spec.ts.snap b/tests/useImplementingTypes/__snapshots__/spec.ts.snap index b1feb25..b3d7c02 100644 --- a/tests/useImplementingTypes/__snapshots__/spec.ts.snap +++ b/tests/useImplementingTypes/__snapshots__/spec.ts.snap @@ -25,10 +25,10 @@ export const mockA = (overrides?: Partial): A => { id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'de4b005e-2b2d-4843-94d1-d356d75d933b', str: overrides && overrides.hasOwnProperty('str') ? overrides.str! : 'cuius', obj: overrides && overrides.hasOwnProperty('obj') ? overrides.obj! : mockB(), - config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : mockTestAConfig() || mockTestTwoAConfig(), - configArray: overrides && overrides.hasOwnProperty('configArray') ? overrides.configArray! : [mockTestAConfig() || mockTestTwoAConfig()], - field: overrides && overrides.hasOwnProperty('field') ? overrides.field! : mockTestTwoAConfig(), - action: overrides && overrides.hasOwnProperty('action') ? overrides.action! : mockTestAction(), + config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : (mockTestAConfig()) || (mockTestTwoAConfig()), + configArray: overrides && overrides.hasOwnProperty('configArray') ? overrides.configArray! : [(mockTestAConfig()) || (mockTestTwoAConfig())], + field: overrides && overrides.hasOwnProperty('field') ? overrides.field! : (mockTestTwoAConfig()), + action: overrides && overrides.hasOwnProperty('action') ? overrides.action! : (mockTestAction()), }; }; @@ -152,9 +152,9 @@ export const mockA = (overrides?: Partial): A => { str: overrides && overrides.hasOwnProperty('str') ? overrides.str! : 'cuius', obj: overrides && overrides.hasOwnProperty('obj') ? overrides.obj! : mockB(), config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : 'Karen.Prosacco@gmail.com', - configArray: overrides && overrides.hasOwnProperty('configArray') ? overrides.configArray! : [mockTestAConfig() || mockTestTwoAConfig()], - field: overrides && overrides.hasOwnProperty('field') ? overrides.field! : mockTestTwoAConfig(), - action: overrides && overrides.hasOwnProperty('action') ? overrides.action! : mockTestAction(), + configArray: overrides && overrides.hasOwnProperty('configArray') ? overrides.configArray! : [(mockTestAConfig()) || (mockTestTwoAConfig())], + field: overrides && overrides.hasOwnProperty('field') ? overrides.field! : (mockTestTwoAConfig()), + action: overrides && overrides.hasOwnProperty('action') ? overrides.action! : (mockTestAction()), }; }; diff --git a/tests/useImplementingTypes/spec.ts b/tests/useImplementingTypes/spec.ts index 226a1a3..4aa410a 100644 --- a/tests/useImplementingTypes/spec.ts +++ b/tests/useImplementingTypes/spec.ts @@ -7,19 +7,19 @@ it('should support useImplementingTypes', async () => { expect(result).toBeDefined(); expect(result).toContain( - "config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : mockTestAConfig() || mockTestTwoAConfig(),", + "config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : (mockTestAConfig()) || (mockTestTwoAConfig()),", ); expect(result).toContain( - "configArray: overrides && overrides.hasOwnProperty('configArray') ? overrides.configArray! : [mockTestAConfig() || mockTestTwoAConfig()],", + "configArray: overrides && overrides.hasOwnProperty('configArray') ? overrides.configArray! : [(mockTestAConfig()) || (mockTestTwoAConfig())],", ); expect(result).toContain( - "field: overrides && overrides.hasOwnProperty('field') ? overrides.field! : mockTestTwoAConfig(),", + "field: overrides && overrides.hasOwnProperty('field') ? overrides.field! : (mockTestTwoAConfig()),", ); expect(result).toContain( - "action: overrides && overrides.hasOwnProperty('action') ? overrides.action! : mockTestAction(),", + "action: overrides && overrides.hasOwnProperty('action') ? overrides.action! : (mockTestAction()),", ); expect(result).toMatchSnapshot(); }); @@ -51,7 +51,7 @@ it(`support useImplementingTypes with fieldGeneration prop`, async () => { ); expect(result).toContain( - "config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : mockTestAConfig() || mockTestTwoAConfig(),", + "config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : (mockTestAConfig()) || (mockTestTwoAConfig()),", ); result = await plugin(testSchema, [], { diff --git a/tests/useImplementingTypesAndDefaultNullableToNull/__snapshots__/spec.ts.snap b/tests/useImplementingTypesAndDefaultNullableToNull/__snapshots__/spec.ts.snap index a3d9dae..415f4ba 100644 --- a/tests/useImplementingTypesAndDefaultNullableToNull/__snapshots__/spec.ts.snap +++ b/tests/useImplementingTypesAndDefaultNullableToNull/__snapshots__/spec.ts.snap @@ -34,7 +34,7 @@ export const mockD = (overrides?: Partial): D => { export const mockTest = (overrides?: Partial): Test => { return { - field1: overrides && overrides.hasOwnProperty('field1') ? overrides.field1! : mockA() || mockB() || mockC() || mockD(), + field1: overrides && overrides.hasOwnProperty('field1') ? overrides.field1! : (mockA()) || (mockB()) || (mockC()) || (mockD()), field2: overrides && overrides.hasOwnProperty('field2') ? overrides.field2! : null, }; }; diff --git a/tests/useImplementingTypesAndDefaultNullableToNull/spec.ts b/tests/useImplementingTypesAndDefaultNullableToNull/spec.ts index 016080a..1de3402 100644 --- a/tests/useImplementingTypesAndDefaultNullableToNull/spec.ts +++ b/tests/useImplementingTypesAndDefaultNullableToNull/spec.ts @@ -11,7 +11,7 @@ it('should support useImplementingTypes', async () => { expect(result).toBeDefined(); expect(result).toContain( - "field1: overrides && overrides.hasOwnProperty('field1') ? overrides.field1! : mockA() || mockB() || mockC() || mockD(),", + "field1: overrides && overrides.hasOwnProperty('field1') ? overrides.field1! : (mockA()) || (mockB()) || (mockC()) || (mockD()),", ); expect(result).toContain("field2: overrides && overrides.hasOwnProperty('field2') ? overrides.field2! : null,"); diff --git a/tests/useImplementingTypesAndTerminateCircularRelationships/__snapshots__/spec.ts.snap b/tests/useImplementingTypesAndTerminateCircularRelationships/__snapshots__/spec.ts.snap index c0dea97..291cc4f 100644 --- a/tests/useImplementingTypesAndTerminateCircularRelationships/__snapshots__/spec.ts.snap +++ b/tests/useImplementingTypesAndTerminateCircularRelationships/__snapshots__/spec.ts.snap @@ -1,5 +1,61 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should parenthesize implementing types with 3+ implementations 1`] = ` +" +export const mockQuery = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): Query => { + const relationshipsToOmit: Set = new Set(_relationshipsToOmit); + relationshipsToOmit.add('Query'); + return { + getData: overrides && overrides.hasOwnProperty('getData') ? overrides.getData! : relationshipsToOmit.has('Container') ? {} as Container : mockContainer({}, relationshipsToOmit), + }; +}; + +export const mockContainer = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): Container => { + const relationshipsToOmit: Set = new Set(_relationshipsToOmit); + relationshipsToOmit.add('Container'); + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'vindico', + dataItem: overrides && overrides.hasOwnProperty('dataItem') ? overrides.dataItem! : (relationshipsToOmit.has('TypeA') ? {} as TypeA : mockTypeA({}, relationshipsToOmit)) || (relationshipsToOmit.has('TypeB') ? {} as TypeB : mockTypeB({}, relationshipsToOmit)) || (relationshipsToOmit.has('TypeC') ? {} as TypeC : mockTypeC({}, relationshipsToOmit)), + }; +}; + +export const mockDataItem = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): DataItem => { + const relationshipsToOmit: Set = new Set(_relationshipsToOmit); + relationshipsToOmit.add('DataItem'); + return { + name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : 'vitiosus', + }; +}; + +export const mockTypeA = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): TypeA => { + const relationshipsToOmit: Set = new Set(_relationshipsToOmit); + relationshipsToOmit.add('TypeA'); + return { + name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : 'demens', + fieldA: overrides && overrides.hasOwnProperty('fieldA') ? overrides.fieldA! : 'occaecati', + }; +}; + +export const mockTypeB = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): TypeB => { + const relationshipsToOmit: Set = new Set(_relationshipsToOmit); + relationshipsToOmit.add('TypeB'); + return { + name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : 'vitiosus', + fieldB: overrides && overrides.hasOwnProperty('fieldB') ? overrides.fieldB! : 'deputo', + }; +}; + +export const mockTypeC = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): TypeC => { + const relationshipsToOmit: Set = new Set(_relationshipsToOmit); + relationshipsToOmit.add('TypeC'); + return { + name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : 'colo', + fieldC: overrides && overrides.hasOwnProperty('fieldC') ? overrides.fieldC! : 'claustrum', + }; +}; +" +`; + exports[`should support useImplementingTypes and terminateCircularRelationships at the same time 1`] = ` " export const mockQuery = (overrides?: Partial, _relationshipsToOmit: Set = new Set()): Query => { @@ -15,7 +71,7 @@ export const mockUser = (overrides?: Partial, _relationshipsToOmit: Set, _relationshi endDate: overrides && overrides.hasOwnProperty('endDate') ? overrides.endDate! : 'adicio', startDate: overrides && overrides.hasOwnProperty('startDate') ? overrides.startDate! : 'altus', timeZone: overrides && overrides.hasOwnProperty('timeZone') ? overrides.timeZone! : 'deprimo', - event: overrides && overrides.hasOwnProperty('event') ? overrides.event! : relationshipsToOmit.has('MeetingEvent') ? {} as MeetingEvent : mockMeetingEvent({}, relationshipsToOmit) || relationshipsToOmit.has('OtherEvent') ? {} as OtherEvent : mockOtherEvent({}, relationshipsToOmit), + event: overrides && overrides.hasOwnProperty('event') ? overrides.event! : (relationshipsToOmit.has('MeetingEvent') ? {} as MeetingEvent : mockMeetingEvent({}, relationshipsToOmit)) || (relationshipsToOmit.has('OtherEvent') ? {} as OtherEvent : mockOtherEvent({}, relationshipsToOmit)), }; }; diff --git a/tests/useImplementingTypesAndTerminateCircularRelationships/spec.ts b/tests/useImplementingTypesAndTerminateCircularRelationships/spec.ts index d3fc387..1ad7eaa 100644 --- a/tests/useImplementingTypesAndTerminateCircularRelationships/spec.ts +++ b/tests/useImplementingTypesAndTerminateCircularRelationships/spec.ts @@ -1,5 +1,6 @@ import { plugin } from '../../src'; import testSchema from './schema'; +import threeImplSchema from './threeImplSchema'; it('should support useImplementingTypes and terminateCircularRelationships at the same time', async () => { const result = await plugin(testSchema, [], { @@ -11,11 +12,28 @@ it('should support useImplementingTypes and terminateCircularRelationships at th expect(result).toBeDefined(); expect(result).toContain( - "events: overrides && overrides.hasOwnProperty('events') ? overrides.events! : [relationshipsToOmit.has('MeetingEvent') ? {} as MeetingEvent : mockMeetingEvent({}, relationshipsToOmit) || relationshipsToOmit.has('OtherEvent') ? {} as OtherEvent : mockOtherEvent({}, relationshipsToOmit)]", + "events: overrides && overrides.hasOwnProperty('events') ? overrides.events! : [(relationshipsToOmit.has('MeetingEvent') ? {} as MeetingEvent : mockMeetingEvent({}, relationshipsToOmit)) || (relationshipsToOmit.has('OtherEvent') ? {} as OtherEvent : mockOtherEvent({}, relationshipsToOmit))]", ); expect(result).toContain( - "event: overrides && overrides.hasOwnProperty('event') ? overrides.event! : relationshipsToOmit.has('MeetingEvent') ? {} as MeetingEvent : mockMeetingEvent({}, relationshipsToOmit) || relationshipsToOmit.has('OtherEvent') ? {} as OtherEvent : mockOtherEvent({}, relationshipsToOmit)", + "event: overrides && overrides.hasOwnProperty('event') ? overrides.event! : (relationshipsToOmit.has('MeetingEvent') ? {} as MeetingEvent : mockMeetingEvent({}, relationshipsToOmit)) || (relationshipsToOmit.has('OtherEvent') ? {} as OtherEvent : mockOtherEvent({}, relationshipsToOmit))", + ); + + expect(result).toMatchSnapshot(); +}); + +it('should parenthesize implementing types with 3+ implementations', async () => { + const result = await plugin(threeImplSchema, [], { + prefix: 'mock', + useImplementingTypes: true, + terminateCircularRelationships: true, + }); + + expect(result).toBeDefined(); + + // Each ternary in the || chain must be wrapped in parentheses + expect(result).toContain( + "dataItem: overrides && overrides.hasOwnProperty('dataItem') ? overrides.dataItem! : (relationshipsToOmit.has('TypeA') ? {} as TypeA : mockTypeA({}, relationshipsToOmit)) || (relationshipsToOmit.has('TypeB') ? {} as TypeB : mockTypeB({}, relationshipsToOmit)) || (relationshipsToOmit.has('TypeC') ? {} as TypeC : mockTypeC({}, relationshipsToOmit))", ); expect(result).toMatchSnapshot(); diff --git a/tests/useImplementingTypesAndTerminateCircularRelationships/threeImplSchema.ts b/tests/useImplementingTypesAndTerminateCircularRelationships/threeImplSchema.ts new file mode 100644 index 0000000..032c8da --- /dev/null +++ b/tests/useImplementingTypesAndTerminateCircularRelationships/threeImplSchema.ts @@ -0,0 +1,31 @@ +import { buildSchema } from 'graphql'; + +export default buildSchema(/* GraphQL */ ` + type Query { + getData(id: String!): Container + } + + type Container { + id: String! + dataItem: DataItem! + } + + interface DataItem { + name: String + } + + type TypeA implements DataItem { + name: String + fieldA: String + } + + type TypeB implements DataItem { + name: String + fieldB: String + } + + type TypeC implements DataItem { + name: String + fieldC: String + } +`);