Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alphabetize fragment ordering in source text #130

Merged
merged 2 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import ApolloAPI

public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration {
public static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? {
public static func cacheKeyInfo(
for type: ApolloAPI.Object,
object: ApolloAPI.ObjectData
) -> ApolloAPI.CacheKeyInfo? {
// Implement this function to configure cache key resolution for your schema types.
return nil
}
Expand Down
54 changes: 54 additions & 0 deletions Tests/ApolloCodegenTests/CodeGenIR/IRRootFieldBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4327,6 +4327,60 @@ class IRRootFieldBuilderTests: XCTestCase {
expect(self.computedReferencedFragments).to(equal(expected))
}

func test__referencedFragments__givenMultipleFragments_hasFragmentsInAlphbeticalOrder() async throws {
// given
schemaSDL = """
type Query {
name: String!
}
"""

document =
"""
query NameQuery {
...Fragment4
...Fragment1
}

fragment Fragment4 on Query {
name
...Fragment3
}

fragment Fragment3 on Query {
name
...Fragment2
}

fragment Fragment2 on Query {
name
}

fragment Fragment1 on Query {
name
...Fragment5
}

fragment Fragment5 on Query {
name
}
"""

// when
try await buildSubjectRootField()

let expected: OrderedSet = await [
try ir.builtFragmentStorage.getFragmentIfBuilt(named: "Fragment1").xctUnwrapped(),
try ir.builtFragmentStorage.getFragmentIfBuilt(named: "Fragment2").xctUnwrapped(),
try ir.builtFragmentStorage.getFragmentIfBuilt(named: "Fragment3").xctUnwrapped(),
try ir.builtFragmentStorage.getFragmentIfBuilt(named: "Fragment4").xctUnwrapped(),
try ir.builtFragmentStorage.getFragmentIfBuilt(named: "Fragment5").xctUnwrapped(),
]

// then
expect(self.computedReferencedFragments).to(equal(expected))
}

// MARK: - Deferred Fragments - hasDeferredFragments property

func test__deferredFragments__givenNoDeferredFragment_hasDeferredFragmentsFalse() async throws {
Expand Down
104 changes: 104 additions & 0 deletions Tests/ApolloCodegenTests/OperationDescriptorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import Foundation
import XCTest
import Nimble
import OrderedCollections
import GraphQLCompiler
@testable import IR
import Utilities
@testable import ApolloCodegenLib
import ApolloCodegenInternalTestHelpers

class OperationDescriptorTests: XCTestCase {
var schemaSDL: String!
var document: String!
var ir: IRBuilder!
var subject: OperationDescriptor!

override func setUp() {
super.setUp()
}

override func tearDown() {
subject = nil
schemaSDL = nil
document = nil
ir = nil
super.tearDown()
}

// MARK: - Helpers

func getOperation(
named operationName: String? = nil,
fromJSONSchema json: Bool = false
) async throws {
ir = json ?
try await .mock(schemaJSON: schemaSDL, document: document) :
try await .mock(schema: schemaSDL, document: document)

var operation: CompilationResult.OperationDefinition
if let operationName = operationName {
operation = try XCTUnwrap(ir.compilationResult.operations.first {$0.name == operationName})
} else {
operation = try XCTUnwrap(ir.compilationResult.operations.first)
}

subject = OperationDescriptor(operation)
}

// MARK: - Tests

@available(macOS 13.0, *)
func test__rawSourceText__givenOperationWithDeeplyNestedFragmentsNotInAlphabeticalOrder__hasReferencedFragmentsInSameOrderAsBuiltOperation() async throws {
// given
schemaSDL = """
type Query {
name: String!
}
"""

document =
"""
query NameQuery {
...Fragment4
...Fragment1
}

fragment Fragment4 on Query {
name
...Fragment3
}

fragment Fragment3 on Query {
name
...Fragment2
}

fragment Fragment2 on Query {
name
}

fragment Fragment1 on Query {
name
...Fragment5
}

fragment Fragment5 on Query {
name
}
"""

try await getOperation(named: "NameQuery")

// when
let operationDescriptorReferencedFragments = subject.rawSourceText
.matches(of: /fragment (\S*)\s/)
.map(\.output.1.description)

let operation = await ir.build(operation: subject.underlyingDefinition)
let builtOperationReferencedFragments = operation.referencedFragments.map(\.name)

// then
expect(operationDescriptorReferencedFragments).to(equal(builtOperationReferencedFragments))
}
}
64 changes: 33 additions & 31 deletions Tests/ApolloCodegenTests/OperationIdentifierFactoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ class OperationIdentifierFactoryTests: XCTestCase {
schemaSDL = nil
document = nil
operation = nil
ir = nil
super.tearDown()
}

// MARK: = Helpers
// MARK: - Helpers

func getOperation(
named operationName: String? = nil,
Expand All @@ -47,43 +48,44 @@ class OperationIdentifierFactoryTests: XCTestCase {

// MARK: - Default Operation Identifier Computation Tests

func test__buildOperation__givenOperationWithNoFragments__hasCorrectOperationIdentifier() async throws {
// given
document = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.GraphQLOperation(named: "HeroAndFriendsNames")
)
func test__identifierForOperation__givenOperationWithNoFragments__hasCorrectOperationIdentifier() async throws {
// given
document = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.GraphQLOperation(named: "HeroAndFriendsNames")
)

schemaSDL = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.JSONSchema)
schemaSDL = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.JSONSchema)

let expected = "1e36c3331171b74c012b86caa04fbb01062f37c61227655d9c0729a62c6f7285"
try await getOperation(named: "HeroAndFriendsNames", fromJSONSchema: true)
let expected = "1e36c3331171b74c012b86caa04fbb01062f37c61227655d9c0729a62c6f7285"
try await getOperation(named: "HeroAndFriendsNames", fromJSONSchema: true)

// when
let actual = try await subject.identifier(for: operation)
// when
let actual = try await subject.identifier(for: operation)

// then
expect(actual).to(equal(expected))
}
// then
expect(actual).to(equal(expected))
}

func test__buildOperation__givenOperationWithFragment__hasCorrectOperationIdentifier() async throws {
// given
document = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.GraphQLOperation(named: "HeroAndFriendsNamesWithFragment")
) + "\n" + String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.GraphQLOperation(named: "HeroName")
)
func test__identifierForOperation__givenOperationWithFragment__hasCorrectOperationIdentifier() async throws {
// given
document = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.GraphQLOperation(named: "HeroAndFriendsNamesWithFragment")
) + "\n" + String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.GraphQLOperation(named: "HeroName")
)

schemaSDL = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.JSONSchema)
schemaSDL = try String(
contentsOf: ApolloCodegenInternalTestHelpers.Resources.StarWars.JSONSchema)

let expected = "599cd7d91ede7a5508cdb26b424e3b8e99e6c2c5575b799f6090695289ff8e99"
try await getOperation(named: "HeroAndFriendsNamesWithFragment", fromJSONSchema: true)
let expected = "599cd7d91ede7a5508cdb26b424e3b8e99e6c2c5575b799f6090695289ff8e99"
try await getOperation(named: "HeroAndFriendsNamesWithFragment", fromJSONSchema: true)

// when
let actual = try await subject.identifier(for: operation)
// when
let actual = try await subject.identifier(for: operation)

// then
expect(actual).to(equal(expected))
}

// then
expect(actual).to(equal(expected))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,38 @@ public struct OperationDescriptor: Sendable {
}

func sourceText(withFormat format: SourceFormat) -> String {
format.formatted(underlyingDefinition)
format.formatted(self)
}


fileprivate var allReferencedFragments: [CompilationResult.FragmentDefinition] {
func insertAllFragments(
from fragment: CompilationResult.FragmentDefinition,
into set: inout Set<CompilationResult.FragmentDefinition>
) {
set.insert(fragment)
for referencedFragment in fragment.referencedFragments {
insertAllFragments(from: referencedFragment, into: &set)
}
}

var fragmentSet = Set<CompilationResult.FragmentDefinition>()
for fragment in underlyingDefinition.referencedFragments {
insertAllFragments(from: fragment, into: &fragmentSet)
}
return fragmentSet.sorted { $0.name < $1.name }
}

}

// MARK: - Formatting

fileprivate extension OperationDescriptor.SourceFormat {
func formatted(_ operation: CompilationResult.OperationDefinition) -> String {
var source = operation.source.convertedToSingleLine()
var set = Set<String>()
append(
to: &source,
set: &set,
fragments: operation.referencedFragments
)
func formatted(_ operation: OperationDescriptor) -> String {
var source = operation.underlyingDefinition.source.convertedToSingleLine()
for fragment in operation.allReferencedFragments {
source += formatted(fragment)
}

switch self {
case .rawSource:
return source
Expand All @@ -86,20 +102,6 @@ fileprivate extension OperationDescriptor.SourceFormat {
}
}

private func append(
to source: inout String,
set: inout Set<String>,
fragments: [CompilationResult.FragmentDefinition]
) {
for fragment in fragments {
if !set.contains(fragment.name) {
set.insert(fragment.name)
source += formatted(fragment)
append(to: &source, set: &set, fragments: fragment.referencedFragments)
}
}
}

private func formatted(_ fragment: CompilationResult.FragmentDefinition) -> String {
switch self {
case .rawSource:
Expand Down
4 changes: 4 additions & 0 deletions apollo-ios-codegen/Sources/IR/IR+RootFieldBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ class RootFieldBuilder {
from: rootSelectionSet
)

referencedFragments.sort(by: {
$0.name < $1.name
})

return Result(
rootField: EntityField(rootField, selectionSet: rootIrSelectionSet),
referencedFragments: referencedFragments,
Expand Down
Loading