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

[Apollo Pagination] Improve ReversePagination support, implement loadAll support, Bidirectional Pagination #115

Merged
merged 178 commits into from
Jan 24, 2024
Merged
Changes from 169 commits
Commits
Show all changes
178 commits
Select commit Hold shift + click to select a range
65312da
Extra conveniences, support throwing transforms
Iron-Ham Oct 12, 2023
a17feea
other func
Iron-Ham Oct 12, 2023
90287b5
Can Load Next as function
Iron-Ham Oct 12, 2023
829f268
Fixups
Iron-Ham Oct 12, 2023
56d6fbb
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 13, 2023
9017cad
Include Test
Iron-Ham Oct 13, 2023
8e82c55
Concurrency and parallelism: Implemented and tested
Iron-Ham Oct 15, 2023
f6daedd
Docs + Touchup
Iron-Ham Oct 15, 2023
ae80176
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 15, 2023
9026824
Fixed tests
Iron-Ham Oct 15, 2023
d78e101
Cleanup
Iron-Ham Oct 15, 2023
2e0c251
linter pass
Iron-Ham Oct 15, 2023
552c689
Return cancellable
Iron-Ham Oct 15, 2023
c11b547
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 17, 2023
a3ee532
Update with experimental Actor change
Iron-Ham Oct 17, 2023
c9f21ac
Nest inside
Iron-Ham Oct 18, 2023
1625e6b
Fix misc.
Iron-Ham Oct 18, 2023
22eb877
Removed unnecessary items
Iron-Ham Oct 18, 2023
dd7ae16
One more
Iron-Ham Oct 18, 2023
6517e54
Fix tests
Iron-Ham Oct 18, 2023
c716df5
No more flaky CI test
Iron-Ham Oct 18, 2023
b392428
Write test for error throw
Iron-Ham Oct 18, 2023
bf386ae
Cleanup
Iron-Ham Oct 18, 2023
147d055
Easier test read
Iron-Ham Oct 18, 2023
f12ba89
undo change
Iron-Ham Oct 18, 2023
e9c136a
Active task should be privte
Iron-Ham Oct 18, 2023
0b24534
include test runs
Iron-Ham Oct 18, 2023
22922fa
Cleanup
Iron-Ham Oct 19, 2023
9a4c30a
Cleanup
Iron-Ham Oct 23, 2023
b0cb840
Cleanup
Iron-Ham Oct 24, 2023
969e6e9
Get rid of PhonyError workaround
Iron-Ham Oct 24, 2023
9c1cfdf
Simpler test for noCache
Iron-Ham Oct 24, 2023
f5b0e8e
Rename test
Iron-Ham Oct 24, 2023
fa5f594
Remove unneded asserts
Iron-Ham Oct 24, 2023
dee9b4c
Remove unused var
Iron-Ham Oct 24, 2023
f58224c
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 24, 2023
be18614
fix: apollo-ios-pagination bug where pages were keyed by initial quer…
JThramer Oct 24, 2023
35d394f
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 24, 2023
3f54a87
Merge branch 'main' of github.com:apollographql/apollo-ios-dev into h…
Iron-Ham Oct 24, 2023
e7174f5
Only run PaginationTests if there are relevant changes
Iron-Ham Oct 25, 2023
f94cf16
Perhaps without the quotes
Iron-Ham Oct 25, 2023
c5781c4
Logic
Iron-Ham Oct 25, 2023
d871c70
order
Iron-Ham Oct 25, 2023
e28f160
debug log
Iron-Ham Oct 25, 2023
c003730
lol
Iron-Ham Oct 25, 2023
16dc299
whoops
Iron-Ham Oct 26, 2023
8992df6
A test
Iron-Ham Oct 26, 2023
8a80040
Swap to non-throwing
Iron-Ham Oct 26, 2023
9890036
Extra safety
Iron-Ham Oct 26, 2023
e1e1482
Update AnyGraphQLPager.swift
Iron-Ham Oct 26, 2023
3e2082d
internal pager
Iron-Ham Oct 26, 2023
2352829
Remove old and inaccurate docs
Iron-Ham Oct 26, 2023
03bd6fe
Merge branch 'hs/support-throwing-transforms' of github.com:Iron-Ham/…
Iron-Ham Oct 26, 2023
624857f
Remove public extension
Iron-Ham Oct 26, 2023
0358db6
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 26, 2023
4c4d3b0
More docs
Iron-Ham Oct 27, 2023
129480f
Minimize change
Iron-Ham Oct 27, 2023
e4c3852
Docs
Iron-Ham Oct 27, 2023
7217089
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 27, 2023
b76c503
Undo
Iron-Ham Oct 28, 2023
03d9808
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Oct 31, 2023
f45044b
Implement loadAllPages function
Iron-Ham Oct 31, 2023
7cd875a
Write test for type erasure
Iron-Ham Oct 31, 2023
06694cb
Add initial task to cancel
Iron-Ham Oct 31, 2023
12d13bc
Remove print statement
Iron-Ham Oct 31, 2023
b4d4163
Explicit reverse pagination
Iron-Ham Oct 31, 2023
cb1e9bd
Specific pagination resolvers
Iron-Ham Oct 31, 2023
dfcf672
Make sure functions are public...
Iron-Ham Oct 31, 2023
fd4a0f5
Cleanup
Iron-Ham Oct 31, 2023
c5226ca
Fixup
Iron-Ham Oct 31, 2023
a526e5f
Use new result struct
Iron-Ham Nov 1, 2023
30d981d
Update type erasure
Iron-Ham Nov 1, 2023
37b4437
Add tests
Iron-Ham Nov 1, 2023
943efda
Spacing
Iron-Ham Nov 1, 2023
f6f2ba5
Public init
Iron-Ham Nov 1, 2023
d7645eb
Bidirectional Pagination
Iron-Ham Nov 1, 2023
b81d165
Linter
Iron-Ham Nov 1, 2023
cee23fd
Merge branch 'main' into hs/support-throwing-transforms
Iron-Ham Nov 8, 2023
d193660
Merge branch 'hs/support-throwing-transforms' into hs/loadAllPages
Iron-Ham Nov 8, 2023
3b45cfb
Merge branch 'hs/loadAllPages' into hs/reverse-pagination
Iron-Ham Nov 8, 2023
b3c5695
Changes from main
Iron-Ham Nov 8, 2023
3a479c8
Add deinit cancellation
Iron-Ham Nov 15, 2023
4780747
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Nov 16, 2023
5914c85
Address memory leaks
Iron-Ham Nov 16, 2023
f8ac16b
Merge branch 'hs/reverse-pagination' of github.com:Iron-Ham/apollo-io…
Iron-Ham Nov 16, 2023
be2e8b4
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Nov 16, 2023
d2b1fbd
Fix flaky test:
Iron-Ham Nov 27, 2023
e1d1e39
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Nov 27, 2023
ac8f4e1
Fix load all
Iron-Ham Nov 28, 2023
700e26b
Dispatch to main actor
Iron-Ham Nov 28, 2023
4d76d87
Review feedback
Iron-Ham Nov 28, 2023
36f47c6
Cancellable options
Iron-Ham Nov 29, 2023
b6ce993
Rename to prevent ambiguity
Iron-Ham Nov 29, 2023
759a4bb
Docs
Iron-Ham Nov 29, 2023
fc0c942
With reload piping
Iron-Ham Nov 29, 2023
cdfb772
Add logger
Iron-Ham Nov 29, 2023
932c928
macOS 11...
Iron-Ham Nov 29, 2023
4c7d551
Linter
Iron-Ham Nov 29, 2023
70a82cc
logger cleanup
Iron-Ham Nov 29, 2023
c9ed511
With cancellation handlers
Iron-Ham Nov 29, 2023
f2971dd
Remove optionality of _subject
Iron-Ham Nov 29, 2023
4072a65
Weak self
Iron-Ham Nov 29, 2023
3958591
Rename
Iron-Ham Nov 30, 2023
c3ce555
Rename
Iron-Ham Nov 30, 2023
d544857
Docs
Iron-Ham Nov 30, 2023
2ce3c8a
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Nov 30, 2023
34e3eb1
Fix flaky
Iron-Ham Nov 30, 2023
1738514
Cleanup2
Iron-Ham Nov 30, 2023
56895c4
One more
Iron-Ham Nov 30, 2023
7c11505
Cleanup
Iron-Ham Dec 1, 2023
041f284
Split
Iron-Ham Dec 1, 2023
2949b51
SIMPLER
Iron-Ham Dec 1, 2023
67aba7e
Simpler
Iron-Ham Dec 1, 2023
46c877b
Docs
Iron-Ham Dec 1, 2023
9b96a00
Remove task cancellation handler
Iron-Ham Dec 1, 2023
7c06197
Whitespace
Iron-Ham Dec 1, 2023
86e3800
Pairing w Anthony
Iron-Ham Dec 1, 2023
f18107e
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Dec 1, 2023
1ebe382
Condense execute function
Iron-Ham Dec 5, 2023
80795ff
Remove redundant code
Iron-Ham Dec 5, 2023
0781750
Memory leaks: Gone
Iron-Ham Dec 7, 2023
8fc2a60
Partial revert
Iron-Ham Dec 7, 2023
97cd964
Rescope
Iron-Ham Dec 7, 2023
c8f4bd2
Slight readability improvement
Iron-Ham Dec 8, 2023
f62c421
Completion manager
Iron-Ham Dec 8, 2023
0530f0e
New test
Iron-Ham Dec 8, 2023
8e9b793
New test
Iron-Ham Dec 8, 2023
01e4dc7
Adjust timings
Iron-Ham Dec 8, 2023
2564ee6
Remove timeout
Iron-Ham Dec 8, 2023
98fc101
Cleaner test
Iron-Ham Dec 9, 2023
272d38c
Remove docblock
Iron-Ham Dec 9, 2023
c791122
Simpler
Iron-Ham Dec 9, 2023
351ce9f
More tests
Iron-Ham Dec 11, 2023
9920b25
Async timings
Iron-Ham Dec 11, 2023
7c5594e
Ditto
Iron-Ham Dec 11, 2023
bf4929b
Organization
Iron-Ham Dec 11, 2023
dc7bed5
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Dec 11, 2023
1b5e5eb
Task Cancellation
Iron-Ham Dec 12, 2023
e0b948b
More tests
Iron-Ham Dec 12, 2023
61d5941
Sync
Iron-Ham Dec 14, 2023
48e3b5d
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Dec 14, 2023
fd8baa3
Add dispatch to improve test reliability
Iron-Ham Dec 15, 2023
81153c3
Move helpers above test
Iron-Ham Dec 15, 2023
b40b4c3
Update apollo-ios-pagination/Sources/ApolloPagination/GraphQLQueryPag…
Iron-Ham Dec 15, 2023
f1b6c34
Clarify that completion blocks can only return paginationerror
Iron-Ham Dec 15, 2023
4b0c973
Remove conditional downcasts
Iron-Ham Dec 15, 2023
f92b550
Updated Publisher
Iron-Ham Dec 15, 2023
1e73845
Rename objects, make Actor public
Iron-Ham Dec 15, 2023
d58f06e
Rename file
Iron-Ham Dec 15, 2023
af28e86
Doc
Iron-Ham Dec 15, 2023
72769ba
Add protocol
Iron-Ham Dec 16, 2023
02d1968
Add AnyAsync wrapper
Iron-Ham Dec 16, 2023
e7a1af8
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Dec 16, 2023
c24b728
Cleanup
Iron-Ham Dec 16, 2023
6a8c47d
Merge branch 'hs/reverse-pagination' of github.com:Iron-Ham/apollo-io…
Iron-Ham Dec 16, 2023
85b498a
And async
Iron-Ham Dec 16, 2023
e647851
Background update
Iron-Ham Dec 16, 2023
18037a1
Allow user selection of queue
Iron-Ham Dec 16, 2023
6a690bb
Update tests
Iron-Ham Dec 16, 2023
0d1c96b
Add to convenience inits
Iron-Ham Dec 16, 2023
8b81f42
Add convenience initalizers
Iron-Ham Dec 16, 2023
233891b
Increased coverage
Iron-Ham Dec 16, 2023
456a36c
Remove unused test dependencies
Iron-Ham Dec 16, 2023
bfe26bf
Delete unused PaginationLogger
Iron-Ham Dec 16, 2023
721660d
Add test for erased cancellation
Iron-Ham Dec 16, 2023
eec09cf
More coverage
Iron-Ham Dec 16, 2023
0cd279b
Add error case
Iron-Ham Dec 18, 2023
1fd458f
Commit test helper file
Iron-Ham Dec 18, 2023
bc1f01e
Update tests
Iron-Ham Dec 18, 2023
cb3f568
Use default qos
Iron-Ham Dec 18, 2023
7bcb178
Update Error? completion block
Iron-Ham Dec 18, 2023
f0c1489
Partial revert default queue
Iron-Ham Dec 20, 2023
33a5fb7
Partial revert
Iron-Ham Dec 20, 2023
d143feb
Fixup
Iron-Ham Dec 20, 2023
86c4885
Fixup2
Iron-Ham Dec 20, 2023
c9c1d34
Try again
Iron-Ham Jan 8, 2024
d9727d9
Cleanup
Iron-Ham Jan 8, 2024
870d2e8
Merge branch 'main' into hs/reverse-pagination
Iron-Ham Jan 24, 2024
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
50 changes: 36 additions & 14 deletions Tests/ApolloInternalTestHelpers/MockGraphQLServer.swift
Original file line number Diff line number Diff line change
@@ -32,22 +32,23 @@ import XCTest
public class MockGraphQLServer {
enum ServerError: Error, CustomStringConvertible {
case unexpectedRequest(String)

public var description: String {
switch self {
case .unexpectedRequest(let requestDescription):
return "Mock GraphQL server received an unexpected request: \(requestDescription)"
}
}
}


public var customDelay: DispatchTimeInterval?
public typealias RequestHandler<Operation: GraphQLOperation> = (HTTPRequest<Operation>) -> JSONObject

private class RequestExpectation<Operation: GraphQLOperation>: XCTestExpectation {
let file: StaticString
let line: UInt
let handler: RequestHandler<Operation>

init(description: String, file: StaticString = #filePath, line: UInt = #line, handler: @escaping RequestHandler<Operation>) {
self.file = file
self.line = line
@@ -56,43 +57,64 @@ public class MockGraphQLServer {
super.init(description: description)
}
}

private let queue = DispatchQueue(label: "com.apollographql.MockGraphQLServer")

public init() { }

// Since RequestExpectation is generic over a specific GraphQLOperation, we can't store these in the dictionary
// directly. Moreover, there is no way to specify the type relationship that holds between the key and value.
// To work around this, we store values as Any and use a generic subscript as a type-safe way to access them.
private var requestExpectations: [AnyHashable: Any] = [:]

private subscript<Operation: GraphQLOperation>(_ operationType: Operation.Type) -> RequestExpectation<Operation>? {
get {
requestExpectations[ObjectIdentifier(operationType)] as! RequestExpectation<Operation>?
}

set {
requestExpectations[ObjectIdentifier(operationType)] = newValue
}
}


private subscript<Operation: GraphQLOperation>(_ operationType: Operation) -> RequestExpectation<Operation>? {
AnthonyMDev marked this conversation as resolved.
Show resolved Hide resolved
get {
requestExpectations[operationType] as! RequestExpectation<Operation>?
}

set {
requestExpectations[operationType] = newValue
}
}

public func expect<Operation: GraphQLOperation>(_ operationType: Operation.Type, file: StaticString = #filePath, line: UInt = #line, requestHandler: @escaping (HTTPRequest<Operation>) -> JSONObject) -> XCTestExpectation {
return queue.sync {
let expectation = RequestExpectation<Operation>(description: "Served request for \(String(describing: operationType))", file: file, line: line, handler: requestHandler)
expectation.assertForOverFulfill = true

self[operationType] = expectation

return expectation
}
}


public func expect<Operation: GraphQLOperation>(_ operation: Operation, file: StaticString = #filePath, line: UInt = #line, requestHandler: @escaping (HTTPRequest<Operation>) -> JSONObject) -> XCTestExpectation {
return queue.sync {
let expectation = RequestExpectation<Operation>(description: "Served request for \(String(describing: operation.self))", file: file, line: line, handler: requestHandler)
expectation.assertForOverFulfill = true

self[operation] = expectation

return expectation
}
}

func serve<Operation>(request: HTTPRequest<Operation>, completionHandler: @escaping (Result<JSONObject, Error>) -> Void) where Operation: GraphQLOperation {
let operationType = type(of: request.operation)

if let expectation = self[operationType] {
if let expectation = self[request.operation] ?? self[operationType] {
// Dispatch after a small random delay to spread out concurrent requests and simulate somewhat real-world conditions.
queue.asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 10...50))) {
queue.asyncAfter(deadline: .now() + (customDelay ?? .milliseconds(Int.random(in: 10...50)))) {
completionHandler(.success(expectation.handler(request)))
expectation.fulfill()
}
13 changes: 7 additions & 6 deletions Tests/ApolloInternalTestHelpers/MockNetworkTransport.swift
Original file line number Diff line number Diff line change
@@ -13,12 +13,12 @@ public final class MockNetworkTransport: RequestChainNetworkTransport {
endpointURL: TestURL.mockServer.url)
self.clientName = clientName
self.clientVersion = clientVersion
}
}

struct TestInterceptorProvider: InterceptorProvider {
let store: ApolloStore
let server: MockGraphQLServer

func interceptors<Operation>(
for operation: Operation
) -> [any ApolloInterceptor] where Operation: GraphQLOperation {
@@ -45,18 +45,18 @@ private class MockGraphQLServerInterceptor: ApolloInterceptor {
let server: MockGraphQLServer

public var id: String = UUID().uuidString

init(server: MockGraphQLServer) {
self.server = server
}

public func interceptAsync<Operation>(chain: RequestChain, request: HTTPRequest<Operation>, response: HTTPResponse<Operation>?, completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) where Operation: GraphQLOperation {
server.serve(request: request) { result in
let httpResponse = HTTPURLResponse(url: TestURL.mockServer.url,
statusCode: 200,
httpVersion: nil,
headerFields: nil)!

switch result {
case .failure(let error):
chain.handleErrorAsync(error,
@@ -68,6 +68,7 @@ private class MockGraphQLServerInterceptor: ApolloInterceptor {
let response = HTTPResponse<Operation>(response: httpResponse,
rawData: data,
parsedResponse: nil)
guard !chain.isCancelled else { return }
chain.proceedAsync(request: request,
response: response,
interceptor: self,
152 changes: 152 additions & 0 deletions Tests/ApolloPaginationTests/AnyAsyncGraphQLQueryPagerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import Apollo
import ApolloAPI
import ApolloInternalTestHelpers
import XCTest

@testable import ApolloPagination

final class AnyAsyncGraphQLQueryPagerTests: XCTestCase {
private typealias Query = MockQuery<Mocks.Hero.FriendsQuery>

private var store: ApolloStore!
private var server: MockGraphQLServer!
private var networkTransport: MockNetworkTransport!
private var client: ApolloClient!

override func setUp() {
super.setUp()
store = ApolloStore(cache: InMemoryNormalizedCache())
server = MockGraphQLServer()
networkTransport = MockNetworkTransport(server: server, store: store)
client = ApolloClient(networkTransport: networkTransport, store: store)
}

func test_concatenatesPages_matchingInitialAndPaginated() async throws {
struct ViewModel {
let name: String
}

let anyPager = await createPager().eraseToAnyPager { data in
data.hero.friendsConnection.friends.map {
ViewModel(name: $0.name)
}
}

let fetchExpectation = expectation(description: "Initial Fetch")
fetchExpectation.assertForOverFulfill = false
let subscriptionExpectation = expectation(description: "Subscription")
subscriptionExpectation.expectedFulfillmentCount = 2
var expectedViewModels: [ViewModel]?
anyPager.subscribe { (result: Result<([ViewModel], UpdateSource), Error>) in
switch result {
case .success((let viewModels, _)):
expectedViewModels = viewModels
fetchExpectation.fulfill()
subscriptionExpectation.fulfill()
default:
XCTFail("Failed to get view models from pager.")
}
}

await fetchFirstPage(pager: anyPager)
await fulfillment(of: [fetchExpectation], timeout: 1)
try await fetchSecondPage(pager: anyPager)

await fulfillment(of: [subscriptionExpectation], timeout: 1.0)
let results = try XCTUnwrap(expectedViewModels)
XCTAssertEqual(results.count, 3)
XCTAssertEqual(results.map(\.name), ["Luke Skywalker", "Han Solo", "Leia Organa"])
}

func test_passesBackSeparateData() async throws {
let anyPager = await createPager().eraseToAnyPager { _, initial, next in
if let latestPage = next.last {
return latestPage.hero.friendsConnection.friends.last?.name
}
return initial.hero.friendsConnection.friends.last?.name
}

let initialExpectation = expectation(description: "Initial")
let secondExpectation = expectation(description: "Second")
var expectedViewModel: String?
anyPager.subscribe { (result: Result<(String?, UpdateSource), Error>) in
switch result {
case .success((let viewModel, _)):
let oldValue = expectedViewModel
expectedViewModel = viewModel
if oldValue == nil {
initialExpectation.fulfill()
} else {
secondExpectation.fulfill()
}
default:
XCTFail("Failed to get view models from pager.")
}
}

await fetchFirstPage(pager: anyPager)
await fulfillment(of: [initialExpectation], timeout: 1.0)
XCTAssertEqual(expectedViewModel, "Han Solo")

try await fetchSecondPage(pager: anyPager)
await fulfillment(of: [secondExpectation], timeout: 1.0)
XCTAssertEqual(expectedViewModel, "Leia Organa")
}

func test_loadAll() async throws {
let pager = createPager()

let firstPageExpectation = Mocks.Hero.FriendsQuery.expectationForFirstPage(server: server)
let lastPageExpectation = Mocks.Hero.FriendsQuery.expectationForSecondPage(server: server)
let loadAllExpectation = expectation(description: "Load all pages")
let subscriber = await pager.subscribe { _ in
loadAllExpectation.fulfill()
}
try await pager.loadAll()
await fulfillment(of: [firstPageExpectation, lastPageExpectation, loadAllExpectation], timeout: 5)
subscriber.cancel()
}

// MARK: - Test helpers

private func createPager() -> AsyncGraphQLQueryPager<Query, Query> {
let initialQuery = Query()
initialQuery.__variables = ["id": "2001", "first": 2, "after": GraphQLNullable<String>.null]
return AsyncGraphQLQueryPager<Query, Query>(
client: client,
initialQuery: initialQuery,
watcherDispatchQueue: .main,
extractPageInfo: { data in
switch data {
case .initial(let data), .paginated(let data):
return CursorBasedPagination.Forward(
hasNext: data.hero.friendsConnection.pageInfo.hasNextPage,
endCursor: data.hero.friendsConnection.pageInfo.endCursor
)
}
},
pageResolver: { pageInfo, direction in
guard direction == .next else { return nil }
let nextQuery = Query()
nextQuery.__variables = [
"id": "2001",
"first": 2,
"after": pageInfo.endCursor,
]
return nextQuery
}
)
}

private func fetchFirstPage<T>(pager: AnyAsyncGraphQLQueryPager<T>) async {
let serverExpectation = Mocks.Hero.FriendsQuery.expectationForFirstPage(server: server)
await pager.fetch()
await fulfillment(of: [serverExpectation], timeout: 1.0)
}

private func fetchSecondPage<T>(pager: AnyAsyncGraphQLQueryPager<T>) async throws {
let serverExpectation = Mocks.Hero.FriendsQuery.expectationForSecondPage(server: server)
try await pager.loadNext()
await fulfillment(of: [serverExpectation], timeout: 1.0)
}
}
Loading
Loading