-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update operation tracker for optimistic updates
Differential Revision: D46991164 fbshipit-source-id: 45ed1c088132a1ac28b5326e79697330eed266bc
- Loading branch information
1 parent
51fee15
commit 900f40c
Showing
6 changed files
with
787 additions
and
1 deletion.
There are no files selected for viewing
282 changes: 282 additions & 0 deletions
282
...elay/relay-hooks/__tests__/FragmentResource-WithOperationTrackerOptimisticUpdates-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow strict-local | ||
* @format | ||
* @oncall relay | ||
*/ | ||
|
||
'use strict'; | ||
import type {LogEvent} from 'relay-runtime/store/RelayStoreTypes'; | ||
|
||
const {getFragment} = require('../../../relay-runtime'); | ||
const {createFragmentResource} = require('../FragmentResource'); | ||
const { | ||
createOperationDescriptor, | ||
createReaderSelector, | ||
graphql, | ||
} = require('relay-runtime'); | ||
const RelayOperationTracker = require('relay-runtime/store/RelayOperationTracker'); | ||
const RelayFeatureFlags = require('relay-runtime/util/RelayFeatureFlags'); | ||
const {createMockEnvironment} = require('relay-test-utils'); | ||
const {disallowWarnings} = require('relay-test-utils-internal'); | ||
|
||
disallowWarnings(); | ||
|
||
describe('FragmentResource with Operation Tracker for optimistic updates behavior', () => { | ||
const componentName = 'TestComponent'; | ||
let environment; | ||
let UserFragment; | ||
let FragmentResource; | ||
let operationTracker; | ||
let nodeOperation; | ||
let nodeOperation2; | ||
let logger; | ||
let UserQuery; | ||
let ViewerFriendsQuery; | ||
let viewerOperation; | ||
|
||
beforeEach(() => { | ||
RelayFeatureFlags.ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES = true; | ||
operationTracker = new RelayOperationTracker(); | ||
logger = jest.fn<[LogEvent], void>(); | ||
environment = createMockEnvironment({ | ||
operationTracker, | ||
log: logger, | ||
}); | ||
|
||
UserFragment = graphql` | ||
fragment FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment on User { | ||
id | ||
name | ||
} | ||
`; | ||
UserQuery = graphql` | ||
query FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery( | ||
$id: ID! | ||
) { | ||
node(id: $id) { | ||
__typename | ||
...FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment | ||
} | ||
} | ||
`; | ||
|
||
ViewerFriendsQuery = graphql` | ||
query FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery { | ||
viewer { | ||
actor { | ||
friends(first: 1) @connection(key: "Viewer_friends") { | ||
edges { | ||
node { | ||
...FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`; | ||
FragmentResource = createFragmentResource(environment); | ||
nodeOperation = createOperationDescriptor(UserQuery, { | ||
id: 'user-id-1', | ||
}); | ||
nodeOperation2 = createOperationDescriptor(UserQuery, { | ||
id: 'user-id-2', | ||
}); | ||
viewerOperation = createOperationDescriptor(ViewerFriendsQuery, {}); | ||
environment.execute({operation: viewerOperation}).subscribe({}); | ||
environment.execute({operation: nodeOperation}).subscribe({}); | ||
environment.subscribe( | ||
environment.lookup(viewerOperation.fragment), | ||
jest.fn(), | ||
); | ||
|
||
// We need to subscribe to a fragment in order for OperationTracker | ||
// to be able to notify owners if they are affected by any pending operation | ||
environment.subscribe( | ||
environment.lookup( | ||
createReaderSelector( | ||
UserFragment, | ||
'user-id-1', | ||
viewerOperation.request.variables, | ||
viewerOperation.request, | ||
), | ||
), | ||
jest.fn(), | ||
); | ||
}); | ||
|
||
afterEach(() => { | ||
RelayFeatureFlags.ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES = false; | ||
}); | ||
|
||
it('should throw promise for pending operation affecting fragment owner', () => { | ||
environment.commitPayload(viewerOperation, { | ||
viewer: { | ||
actor: { | ||
id: 'viewer-id', | ||
__typename: 'User', | ||
friends: { | ||
pageInfo: { | ||
hasNextPage: true, | ||
hasPrevPage: false, | ||
startCursor: 'cursor-1', | ||
endCursor: 'cursor-1', | ||
}, | ||
edges: [ | ||
{ | ||
cursor: 'cursor-1', | ||
node: { | ||
id: 'user-id-1', | ||
name: 'Alice', | ||
__typename: 'User', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const fragmentRef = { | ||
__id: 'user-id-1', | ||
__fragments: { | ||
FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment: {}, | ||
}, | ||
__fragmentOwner: nodeOperation.request, | ||
}; | ||
|
||
const result = FragmentResource.read( | ||
getFragment(UserFragment), | ||
fragmentRef, | ||
componentName, | ||
); | ||
FragmentResource.subscribe(result, jest.fn()); | ||
|
||
// Execute the nodeOperation as a mutation and set the record as undefined in optimistic updater | ||
environment | ||
.executeMutation({ | ||
operation: nodeOperation, | ||
optimisticUpdater: store => { | ||
const record = store.get('user-id-1'); | ||
record?.setValue(undefined, 'name'); | ||
}, | ||
}) | ||
.subscribe({}); | ||
|
||
// Check the pending opeartion for both the node and viewer query | ||
const pendingOperationsForViewerOperation = | ||
operationTracker.getPendingOperationsAffectingOwner( | ||
viewerOperation.request, | ||
)?.promise; | ||
expect(pendingOperationsForViewerOperation).not.toBe(null); | ||
|
||
const pendingOperationsForNodeOperation = | ||
operationTracker.getPendingOperationsAffectingOwner( | ||
nodeOperation.request, | ||
)?.promise; | ||
expect(pendingOperationsForNodeOperation).not.toBe(null); | ||
}); | ||
|
||
it('when an unrelated operation resolves while an optimistic response is currently applied', () => { | ||
environment.commitPayload(viewerOperation, { | ||
viewer: { | ||
actor: { | ||
id: 'viewer-id', | ||
__typename: 'User', | ||
friends: { | ||
pageInfo: { | ||
hasNextPage: true, | ||
hasPrevPage: false, | ||
startCursor: 'cursor-1', | ||
endCursor: 'cursor-2', | ||
}, | ||
edges: [ | ||
{ | ||
cursor: 'cursor-1', | ||
node: { | ||
id: 'user-id-1', | ||
name: 'Alice', | ||
__typename: 'User', | ||
}, | ||
}, | ||
{ | ||
cursor: 'cursor-2', | ||
node: { | ||
id: 'user-id-2', | ||
name: 'Bob', | ||
__typename: 'User', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const fragmentRef = { | ||
__id: 'user-id-1', | ||
__fragments: { | ||
FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment: {}, | ||
}, | ||
__fragmentOwner: nodeOperation.request, | ||
}; | ||
const fragmentRef2 = { | ||
__id: 'user-id-2', | ||
__fragments: { | ||
FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment: {}, | ||
}, | ||
__fragmentOwner: nodeOperation2.request, | ||
}; | ||
|
||
const result = FragmentResource.read( | ||
getFragment(UserFragment), | ||
fragmentRef, | ||
componentName, | ||
); | ||
FragmentResource.subscribe(result, jest.fn()); | ||
const result2 = FragmentResource.read( | ||
getFragment(UserFragment), | ||
fragmentRef2, | ||
componentName, | ||
); | ||
FragmentResource.subscribe(result2, jest.fn()); | ||
|
||
// Execute the nodeOperation as a mutation and set the record as undefined in optimistic updater | ||
environment | ||
.executeMutation({ | ||
operation: nodeOperation, | ||
optimisticUpdater: store => { | ||
const record = store.get('user-id-1'); | ||
record?.setValue(undefined, 'name'); | ||
}, | ||
}) | ||
.subscribe({}); | ||
environment | ||
.executeMutation({ | ||
operation: nodeOperation2, | ||
optimisticUpdater: store => { | ||
const record = store.get('user-id-2'); | ||
record?.setValue(undefined, 'name'); | ||
}, | ||
}) | ||
.subscribe({}); | ||
|
||
const pendingOperationsForNodeOperation = | ||
operationTracker.getPendingOperationsAffectingOwner( | ||
nodeOperation.request, | ||
); | ||
expect(pendingOperationsForNodeOperation?.pendingOperations.length).toBe(1); | ||
const pendingOperationsForNodeOperation2 = | ||
operationTracker.getPendingOperationsAffectingOwner( | ||
nodeOperation2.request, | ||
); | ||
expect(pendingOperationsForNodeOperation2?.pendingOperations.length).toBe( | ||
1, | ||
); | ||
}); | ||
}); |
67 changes: 67 additions & 0 deletions
67
..._generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql.js
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.