Skip to content

Commit 9c9efcc

Browse files
calvincestarigh-action-runner
authored andcommitted
fix: Local cache mutation generation when field merging disabled (#654)
1 parent 8097340 commit 9c9efcc

File tree

3 files changed

+225
-18
lines changed

3 files changed

+225
-18
lines changed

Tests/ApolloCodegenTests/ApolloCodegenTests.swift

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2904,4 +2904,172 @@ class ApolloCodegenTests: XCTestCase {
29042904

29052905
}
29062906

2907+
// MARK: - Local Cache Mutation + Field Merging Integration Tests
2908+
//
2909+
// These are integration tests because the codegen test wrapper infrastructure does not support overriding config
2910+
// values during the test.
2911+
2912+
func test__fileRendering__givenLocalCacheMutationQuery_whenSelectionSetInitializersEmpty_andFileMergingNone_shouldGenerateFullSelectionSetInitializers() async throws {
2913+
// given
2914+
try createFile(
2915+
body: """
2916+
type Query {
2917+
allAnimals: [Animal!]
2918+
}
2919+
2920+
interface Animal {
2921+
species: String
2922+
}
2923+
""",
2924+
filename: "schema.graphqls"
2925+
)
2926+
2927+
try createFile(
2928+
body: """
2929+
query TestOperation @apollo_client_ios_localCacheMutation {
2930+
allAnimals {
2931+
species
2932+
}
2933+
}
2934+
""",
2935+
filename: "operation.graphql"
2936+
)
2937+
2938+
let fileManager = MockApolloFileManager(strict: false)
2939+
let expectation = expectation(description: "Received local cache mutation file data.")
2940+
2941+
fileManager.mock(closure: .createFile({ path, data, attributes in
2942+
if path.hasSuffix("TestOperationLocalCacheMutation.graphql.swift") {
2943+
expect(data?.asString).to(equalLineByLine("""
2944+
init(
2945+
allAnimals: [AllAnimal]? = nil
2946+
) {
2947+
""", atLine: 26, ignoringExtraLines: true))
2948+
2949+
expectation.fulfill()
2950+
}
2951+
2952+
return true
2953+
}))
2954+
2955+
// when
2956+
let config = ApolloCodegen.ConfigurationContext(
2957+
config: ApolloCodegenConfiguration.mock(
2958+
input: .init(
2959+
schemaSearchPaths: [directoryURL.appendingPathComponent("schema.graphqls").path],
2960+
operationSearchPaths: [directoryURL.appendingPathComponent("operation.graphql").path]
2961+
),
2962+
// Apollo codegen should override the next two value to force the generation of selection set initializers
2963+
// and perform all file merging for the local cache mutation.
2964+
options: .init(selectionSetInitializers: []),
2965+
experimentalFeatures: .init(fieldMerging: .none)
2966+
),
2967+
rootURL: nil
2968+
)
2969+
2970+
let subject = ApolloCodegen(
2971+
config: config,
2972+
operationIdentifierFactory: OperationIdentifierFactory(),
2973+
itemsToGenerate: .code
2974+
)
2975+
2976+
let compilationResult = try await subject.compileGraphQLResult()
2977+
let ir = IRBuilder(compilationResult: compilationResult)
2978+
2979+
try await subject.generateFiles(
2980+
compilationResult: compilationResult,
2981+
ir: ir,
2982+
fileManager: fileManager
2983+
)
2984+
2985+
// then
2986+
expect(fileManager.allClosuresCalled).to(beTrue())
2987+
2988+
await fulfillment(of: [expectation], timeout: 1)
2989+
}
2990+
2991+
func test__fileRendering__givenLocalCacheMutationFragment_whenSelectionSetInitializersEmpty_andFileMergingNone_shouldGenerateFullSelectionSetInitializers() async throws {
2992+
// given
2993+
try createFile(
2994+
body: """
2995+
type Query {
2996+
allAnimals: [Animal!]
2997+
}
2998+
2999+
interface Animal {
3000+
species: String
3001+
}
3002+
""",
3003+
filename: "schema.graphqls"
3004+
)
3005+
3006+
try createFile(
3007+
body: """
3008+
query TestOperation {
3009+
allAnimals {
3010+
...PredatorFragment
3011+
}
3012+
}
3013+
3014+
fragment PredatorFragment on Animal @apollo_client_ios_localCacheMutation {
3015+
species
3016+
}
3017+
""",
3018+
filename: "operation.graphql"
3019+
)
3020+
3021+
let fileManager = MockApolloFileManager(strict: false)
3022+
let expectation = expectation(description: "Received local cache mutation file data.")
3023+
3024+
fileManager.mock(closure: .createFile({ path, data, attributes in
3025+
if path.hasSuffix("PredatorFragment.graphql.swift") {
3026+
expect(data?.asString).to(equalLineByLine("""
3027+
init(
3028+
__typename: String,
3029+
species: String? = nil
3030+
) {
3031+
""", atLine: 26, ignoringExtraLines: true))
3032+
3033+
expectation.fulfill()
3034+
}
3035+
3036+
return true
3037+
}))
3038+
3039+
// when
3040+
let config = ApolloCodegen.ConfigurationContext(
3041+
config: ApolloCodegenConfiguration.mock(
3042+
input: .init(
3043+
schemaSearchPaths: [directoryURL.appendingPathComponent("schema.graphqls").path],
3044+
operationSearchPaths: [directoryURL.appendingPathComponent("operation.graphql").path]
3045+
),
3046+
// Apollo codegen should override the next two value to force the generation of selection set initializers
3047+
// and perform all file merging for the local cache mutation.
3048+
options: .init(selectionSetInitializers: []),
3049+
experimentalFeatures: .init(fieldMerging: .none)
3050+
),
3051+
rootURL: nil
3052+
)
3053+
3054+
let subject = ApolloCodegen(
3055+
config: config,
3056+
operationIdentifierFactory: OperationIdentifierFactory(),
3057+
itemsToGenerate: .code
3058+
)
3059+
3060+
let compilationResult = try await subject.compileGraphQLResult()
3061+
let ir = IRBuilder(compilationResult: compilationResult)
3062+
3063+
try await subject.generateFiles(
3064+
compilationResult: compilationResult,
3065+
ir: ir,
3066+
fileManager: fileManager
3067+
)
3068+
3069+
// then
3070+
expect(fileManager.allClosuresCalled).to(beTrue())
3071+
3072+
await fulfillment(of: [expectation], timeout: 1)
3073+
}
3074+
29073075
}

apollo-ios-codegen/Sources/ApolloCodegenLib/ApolloCodegen.swift

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,34 +299,68 @@ public class ApolloCodegen {
299299
let mergeNamedFragmentFields = config.experimentalFeatures.fieldMerging.options
300300
.contains(.namedFragments)
301301

302+
/// A `ConfigurationContext` to use when generated local cache mutations.
303+
///
304+
/// Local cache mutations require some codegen options to be overridden to generate valid objects.
305+
/// This context overrides only the necessary properties, copying all other values from the user-provided `context`.
306+
lazy var cacheMutationContext: ConfigurationContext = {
307+
ConfigurationContext(
308+
config: ApolloCodegenConfiguration(
309+
schemaNamespace: self.config.schemaNamespace,
310+
input: self.config.input,
311+
output: self.config.output,
312+
options: self.config.options,
313+
experimentalFeatures: ApolloCodegenConfiguration.ExperimentalFeatures(
314+
fieldMerging: .all,
315+
legacySafelistingCompatibleOperations: self.config.experimentalFeatures.legacySafelistingCompatibleOperations
316+
),
317+
schemaDownload: self.config.schemaDownload,
318+
operationManifest: self.config.operationManifest
319+
),
320+
rootURL: self.config.rootURL
321+
)
322+
}()
323+
302324
return try await nonFatalErrorCollectingTaskGroup() { group in
303325
for fragment in fragments {
326+
let fragmentConfig = fragment.isLocalCacheMutation ? cacheMutationContext : self.config
327+
304328
group.addTask {
305329
let irFragment = await ir.build(
306330
fragment: fragment,
307-
mergingNamedFragmentFields: mergeNamedFragmentFields
331+
mergingNamedFragmentFields: fragment.isLocalCacheMutation ? true : mergeNamedFragmentFields
308332
)
309333

310-
let errors = try await FragmentFileGenerator(irFragment: irFragment, config: self.config)
311-
.generate(forConfig: self.config, fileManager: fileManager)
334+
let errors = try await FragmentFileGenerator(
335+
irFragment: irFragment,
336+
config: fragmentConfig
337+
).generate(
338+
forConfig: fragmentConfig,
339+
fileManager: fileManager
340+
)
312341
return (irFragment.name, errors)
313342
}
314343
}
315344

316345
for operation in operations {
346+
let operationConfig = operation.isLocalCacheMutation ? cacheMutationContext : self.config
347+
317348
group.addTask {
318349
async let identifier = self.operationIdentifierFactory.identifier(for: operation)
319350

320351
let irOperation = await ir.build(
321352
operation: operation,
322-
mergingNamedFragmentFields: mergeNamedFragmentFields
353+
mergingNamedFragmentFields: operation.isLocalCacheMutation ? true : mergeNamedFragmentFields
323354
)
324355

325356
let errors = try await OperationFileGenerator(
326357
irOperation: irOperation,
327358
operationIdentifier: await identifier,
328-
config: self.config
329-
).generate(forConfig: self.config, fileManager: fileManager)
359+
config: operationConfig
360+
).generate(
361+
forConfig: operationConfig,
362+
fileManager: fileManager
363+
)
330364
return (irOperation.name, errors)
331365
}
332366
}

apollo-ios-codegen/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration.swift

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,30 +1434,35 @@ extension ApolloCodegenConfiguration.OperationsFileOutput {
14341434
extension ApolloCodegenConfiguration {
14351435
/// Determine whether the operations files are output to the schema types module.
14361436
func shouldGenerateSelectionSetInitializers(for operation: IR.Operation) -> Bool {
1437-
guard experimentalFeatures.fieldMerging == .all else { return false }
1438-
14391437
if operation.definition.isLocalCacheMutation {
14401438
return true
14411439

1442-
} else if options.selectionSetInitializers.contains(.operations) {
1443-
return true
1444-
14451440
} else {
1446-
return options.selectionSetInitializers.contains(definitionNamed: operation.definition.name)
1441+
guard experimentalFeatures.fieldMerging == .all else { return false }
1442+
1443+
if options.selectionSetInitializers.contains(.operations) {
1444+
return true
1445+
1446+
} else {
1447+
return options.selectionSetInitializers.contains(definitionNamed: operation.definition.name)
1448+
}
14471449
}
14481450
}
14491451

14501452
/// Determine whether the operations files are output to the schema types module.
14511453
func shouldGenerateSelectionSetInitializers(for fragment: IR.NamedFragment) -> Bool {
1452-
guard experimentalFeatures.fieldMerging == .all else { return false }
1453-
1454-
if options.selectionSetInitializers.contains(.namedFragments) { return true }
1455-
14561454
if fragment.definition.isLocalCacheMutation {
14571455
return true
1458-
}
14591456

1460-
return options.selectionSetInitializers.contains(definitionNamed: fragment.definition.name)
1457+
} else {
1458+
guard experimentalFeatures.fieldMerging == .all else { return false }
1459+
1460+
if options.selectionSetInitializers.contains(.namedFragments) {
1461+
return true
1462+
} else {
1463+
return options.selectionSetInitializers.contains(definitionNamed: fragment.definition.name)
1464+
}
1465+
}
14611466
}
14621467
}
14631468

0 commit comments

Comments
 (0)