diff --git a/Sources/Apollo/GraphQLQueryWatcher.swift b/Sources/Apollo/GraphQLQueryWatcher.swift index 576757921d..5d73cb01a2 100644 --- a/Sources/Apollo/GraphQLQueryWatcher.swift +++ b/Sources/Apollo/GraphQLQueryWatcher.swift @@ -74,9 +74,14 @@ public final class GraphQLQueryWatcher: Cancellable, Apollo guard let self = self else { return } switch result { - case .success: + case .success(let graphQLResult): self.callbackQueue.async { [weak self] in - self?.resultHandler(result) + guard let self = self else { + return + } + + self.dependentKeys = graphQLResult.dependentKeys + self.resultHandler(result) } case .failure: // If the cache fetch is not successful, for instance if the data is missing, refresh from the server. diff --git a/Sources/ApolloTestSupport/MockNetworkTransport.swift b/Sources/ApolloTestSupport/MockNetworkTransport.swift index 4011ac69a0..ee1cb8566e 100644 --- a/Sources/ApolloTestSupport/MockNetworkTransport.swift +++ b/Sources/ApolloTestSupport/MockNetworkTransport.swift @@ -2,7 +2,7 @@ import Apollo import Dispatch public final class MockNetworkTransport: NetworkTransport { - let body: JSONObject + public var body: JSONObject public var clientName = "MockNetworkTransport" public var clientVersion = "mock_version" diff --git a/Tests/ApolloCacheDependentTests/WatchQueryTests.swift b/Tests/ApolloCacheDependentTests/WatchQueryTests.swift index e144a70c4a..30138520cb 100644 --- a/Tests/ApolloCacheDependentTests/WatchQueryTests.swift +++ b/Tests/ApolloCacheDependentTests/WatchQueryTests.swift @@ -575,4 +575,126 @@ class WatchQueryTests: XCTestCase, CacheTesting { waitForExpectations(timeout: 5, handler: nil) } } + + func testWatchedQueryDependentKeysAreUpdated() { + withCache { cache in + let networkTransport = MockNetworkTransport(body: [ + "data": [ + "hero": [ + "id": "0", + "name": "Artoo", + "__typename": "Droid", + "friends": [ + [ + "id": "10", + "__typename": "Human", + "name": "Luke Skywalker" + ] + ] + ] + ] + ]) + + let store = ApolloStore(cache: cache) + let client = ApolloClient(networkTransport: networkTransport, store: store) + client.store.cacheKeyForObject = { $0["id"] } + + let query = HeroAndFriendsNamesWithIDsQuery() + let hasPicardFriendExpecation = self.expectation(description: "Has friend named Jean-Luc Picard") + let hasHanSoloFriendExpecation = self.expectation(description: "Has friend named Han Solo") + let initialFetchExpectation = self.expectation(description: "Initial fetch") + var isInitialFetch = true + var expectedDependentKeys = [ + "0.__typename", + "0.friends", + "0.id", + "0.name", + "10.__typename", + "10.id", + "10.name", + "QUERY_ROOT.hero", + ] + + _ = client.watch(query: query) { result in + defer { + if isInitialFetch { + isInitialFetch = false + initialFetchExpectation.fulfill() + } + } + switch result { + case .success(let graphQLResult): + XCTAssertEqual(graphQLResult.dependentKeys?.sorted(), expectedDependentKeys) + guard let friends = graphQLResult.data?.hero?.friends else { + XCTFail("404 friends not found") + return + } + + if friends.contains(where: { $0?.name == "Jean-Luc Picard" }) { + hasPicardFriendExpecation.fulfill() + } + if friends.contains(where: { $0?.name == "Han Solo" }) { + hasHanSoloFriendExpecation.fulfill() + } + case .failure(let error): + XCTFail("Watcher error: \(error)") + } + } + wait(for: [initialFetchExpectation], timeout: 1) + + /// Add an additional friend to the results so that the watcher for this query knows to look for updates to friend #11 + let updateInitialQueryExpectation = self.expectation(description: "Update initial query") + store.withinReadWriteTransaction({ transaction in + try transaction.update(query: query) { (data: inout HeroAndFriendsNamesWithIDsQuery.Data) in + data.hero?.friends?.append(try .init(jsonObject: [ + "id": "11", + "__typename": "Human", + "name": "Jean-Luc Picard" + ])) + updateInitialQueryExpectation.fulfill() + } + }) + + /// The dependent keys should have changed here since we've now added a new friend to the user's friends. + expectedDependentKeys = [ + "0.__typename", + "0.friends", + "0.id", + "0.name", + "10.__typename", + "10.id", + "10.name", + "11.__typename", + "11.id", + "11.name", + "QUERY_ROOT.hero" + ] + + wait(for: [updateInitialQueryExpectation, hasPicardFriendExpecation], timeout: 1) + + + /// Send an update that updates friend #11 on a different query + networkTransport.body = [ + "data": [ + "hero": [ + "id": "2", + "name": "R2-D2", + "__typename": "Droid", + "friends": [ + [ + "id": "11", + "__typename": "Human", + "name": "Han Solo" + ] + ] + ] + ] + ] + + /// This fetch should trigger our watcher on friend #11 + client.fetch(query: HeroAndFriendsNamesWithIDsQuery(episode: .newhope), cachePolicy: .fetchIgnoringCacheData) + + self.wait(for: [hasHanSoloFriendExpecation], timeout: 1) + } + } }